mcpField
U
uncomplect

uncomplect

github.com/joelhooks开发3787 次安装5 天前更新
代码仓库

关于 uncomplect

面向开发者和编码 Agent 的推理技能,指导以替换为导向的软件设计。它要求建议基于代码与运行证据,并着重拆分关注点、降低复杂度和提升模块可替换性。

主要功能

  1. 01依据代码、测试和运行证据提出建议
  2. 02梳理相互纠缠的关注点
  3. 03设计精简接口与纯决策逻辑
  4. 04规划替换、删除项和防护措施

适用场景

  1. 01在重大重写前审查系统设计
  2. 02找出耦合的状态、策略与基础设施
  3. 03规划保留已验证行为的替换方案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/joelhooks/skills --skill uncomplect

安装后在 Claude Code 中输入/uncomplect确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
benchmark.md
evals.json
trigger-evals.json
references/
resilience-and-durability.md
source-map.md

详细介绍

解开纠缠

把当前系统视为功能原型。它证明了哪些行为很重要,但没有证明其结构是正确的。保留行为和证据,让偶然形成的结构随之消失。

目标是减少交织在一起的关注点,而不是减少行数。添加一个类型并删除三个特殊情况的替换方案更简单。添加一层却什么也没删除的替换方案则不然。

先阅读

基于对代码猜测得出的建议,往往是在修复一个并不存在的系统,因此每条建议都要以你读到的内容为依据。从最相关的项目说明、决策、源代码、测试、运行记录和当前问题着手。

对于状态机代码,要阅读状态机、持久化状态、恢复路径、调用的 Actor、守卫条件和转换测试。对于效应系统代码,要阅读项目锁定的版本及其现有服务、错误、重试和事务边界,并根据该版本的源代码核对 API。

只有当每条建议都指出其依据的事实、要保留的行为以及要删除的复杂性时,才算完成。

Hickey:分离交织的关注点

纠缠是指两个独立关注点编织在一起,以至于其中一个无法单独改变。为它们建立一张表:

关注点交织在一起的是什么?独立的值分离方式

常见的交织:

  • 值与时间混杂;
  • 标识与不断变化的状态混杂;
  • 行为与存储或传输混杂;
  • 行为与调用它的接口混杂,导致同一规则分别为 CLI、HTTP 路由和 Agent 工具重复编写;
  • 策略与机制混杂;
  • 当前事实与历史事实混杂;
  • 生命周期状态与领域数据混杂;
  • 重试策略与业务失败混杂;
  • 一种通用状态掩盖了不同事实。

优先使用不可变值和纯函数。用类型定义一次行为及其输入、输出和失败情形,并基于该定义派生出各个接口。历史事实依然为真,但它们只报告发生了什么,不决定接下来会发生什么。

Ousterhout:构建深层接口

深层模块用小型接口隐藏棘手问题。应根据变更放大效应、认知负担和未知的未知因素来评估设计。

对于每个拟议的类型、状态、服务、接口或配置值,都要问:

  1. 这个元素消除了什么复杂性?
  2. 它是否用小型接口隐藏了棘手问题?
  3. API 能否从定义上消除意外错误或特殊情况?
  4. 它是将复杂性集中到一个模块中,还是把策略分散到各个调用方?
  5. 添加它之后,我们能删除什么?

如果基础设施只增加名称,却没有减少依赖或晦涩之处,就会让系统更难阅读,因此不要引入它。

对于影响重大的替换方案,要设计两次。先勾勒出两个实质上不同的接口,再比较调用方的负担、隐藏的知识、依赖关系、失败语义,以及每个方案能删除什么,然后再做选择。

Young:以删除为优化目标

Greg Young 的 The Art of Destroying Software 主张构建小型程序,在其模型不再符合现实时可以将其替换。只有当一个单元包含自身所需的一切,且没有其他部分依赖其内部实现时,替换才会很容易。

请问:

  1. 能否依据契约、测试和运行记录,在大约一周内重写这个模块?
  2. 如果不能,是哪些依赖、隐藏的不变量或共享状态让替换变得危险?
  3. 这个单元是否拥有自己的配置、存储和基础设施,从而在移除它时一并移除这些内容?
  4. 如果把它移除,其余部分能否继续构建,并由编译器指出所有使用过它的位置?
  5. 新实现能否通过相同的接缝与旧实现并行运行?
  6. 切换完成后,哪些内容可以删除?

一周这个期限是一种压力测试。小型、可替换的单元可以共享一个进程和一个代码库;拆分服务则是另一项决策。

DDD:在正确的边界中放置权威

用领域语言而非存储语言,为限界上下文及其信任边界命名。

明确:

  • 哪个聚合负责维护每项不变量;
  • 不可变事实与当前决策;
  • 命令、事件、策略和读模型;
  • 上游和下游的契约归属;
  • 上下文边界处的转换;
  • 隐藏了多种领域含义的术语。

读模型(DDD 中的投影)可以报告决策,但只有负责维护不变量的聚合才能作出决策。如果仪表板或缓存开始作出决策,就意味着现在有两个地方掌握真相。

将工作流建模为命令,以及由当前事实产生的显式事件。纯工作流返回事件;发布事件和持久化事件是独立的副作用。

类型化副作用:保持纯核心纯粹

效应系统适用于有副作用的边界。用效应语法编写纯规则看似复杂,反而掩盖了其简单性。

  • 将领域决策作为对带标签值进行操作的纯函数。
  • 在边界处解码不可信输入,让内部逻辑全部使用已解析的领域类型。
  • 将数据库、网络、文件系统、时钟、配置和服务商相关操作放在具名服务之后。
  • 将预期故障建模为类型化错误,并将缺陷单独处理。
  • 仅对类型化的暂时性故障进行重试,并且只在已证明操作幂等时重试。
  • 将服务商调用放在权威数据库事务之外。
  • 每个工作流只使用一个版本的效应系统。

如果项目在此边界没有兼容的效应系统,那么使用语言原生代码更简单。把框架迁移说成简化,是另一项独立的工作。

生命周期:让关键时间显式可见

当模式变化会影响哪些事件合法,或工作需要取消、重试、恢复执行或子任务时,使用显式状态机。解析、验证和一次性迁移则使用普通函数。

  • 状态表示允许事件不同的模式。
  • 上下文只保存最少的参与者数据,不要复制状态标签。
  • 守卫应是纯函数且同步执行。
  • 副作用应放在被调用的 Actor 或边界服务中。
  • 在持久化状态获得当前权威性之前,对其进行规范化。
  • 暂时性计算不需要持久化状态。
  • 恢复测试应跨越版本和部署边界。

对于崩溃恢复、重复投递、过载或重放,请使用韧性与持久性术语。将执行、持久化状态、安全重复执行和恢复责任区分开来。

由经过验证的领域值驱动的小型状态机,胜过一个需要解释原始数据行的大型状态机。

面向 Agent 的上下文:让分离始终有效

Agent 会照着已有模式复制。若分离只存在于某位审查者的脑海中,下一次变更就会再次把它们编织在一起。Lauren Tan 的 Dune 旨在实现“捷径、最简单的路径,就是适合 Agent 的正确路径”。她提出了一个阶梯,说明修正规则应放在哪一层,按难度从高到低依次是代码库、静态分析、规则、技能和风格指南。

对于你提出的每项分离:

  1. 为每种关注点指定一个具名位置,例如文件夹、软件包或类型,让目录树展示代码在哪里运行,以及哪些导入合法。
  2. 选择能够承载它的最高层级:先用让交织无法写出的类型或 API,其次用 lint 规则或导入边界,最后才写成规则。
  3. 只保留一条铺平的路径。同一件事有两种做法时,删掉一种。

只修改一个文件中的少量内容,也应能保持整个系统正确。如果做不到,说明 Agent 所需的上下文太庞大,这是一个设计问题。

替换审查

按以下顺序进行:

  1. 核心行为:原型证明了什么,必须保留什么。
  2. 复杂性映射:当前复杂性是如何交织在一起的。
  3. 领域映射:上下文、聚合、不变量、命令、事件、读模型。
  4. 深层接缝:能够隐藏不可避免复杂性的最小公开操作。
  5. 副作用边界:纯核心、类型化失败、副作用适配器、事务边界、重试负责人。
  6. 生命周期:仍能明确体现时间的最小状态机。
  7. 重写门槛:哪些因素会让这个单元无法在大约一周内被替换。
  8. 删除清单:替换方案会移除的状态、标志、异常、操作步骤和模块。
  9. 围栏:防止每项分离重新混在一起的类型、lint 规则或边界。
  10. 采用路径:无需两个权威来源的跟踪器、影子比对、切换和回滚。
  11. 未知项清单:已知事实、已知的未知项、未知的已知项、疑似未知的未知项。

这是一份推理检查清单。如果一个表格或类型定义就能承载相关证据,可以合并条目;审查范围应与决策风险相称。

输出

首先给出一个结论,只能是 局部更复杂,整体更简单 或 机制更多,没有净简化。

然后提供基于源代码的复杂性表、拟议的领域语言、一个深层接口、纯决策类型、副作用与生命周期的划分、新旧状态数量、重写门槛、删除清单、各项分离的围栏,以及风险和一个关键问题。

说明哪些事情会变得不可能、哪些会变得一目了然、哪些会消失。不要赞扬;删除清单足以说明理由。

简洁调用

Treat the current system as a functional prototype, not an architecture to preserve. Keep its proven behavior. Find what is complected. Separate values from time, policy from mechanism, behavior from the interfaces that reach it, and facts from read models. Define accidental errors out of existence. Draw the domain boundaries and aggregate invariants. Keep effects at effectful boundaries and state machines on essential lifecycle. Make each unit carry its own infrastructure so it can be deleted whole. Enforce each separation with a type or lint rule so the next change cannot braid it again. Propose the smallest deep replacement, show what it deletes, and name the remaining unknowns.

有关源代码映射及各视角的作用,请参阅 references/source-map.md。有关基于这些原则构建的技术栈,请参阅 rat-stack。

评论

支持 Markdown