关于 moai-workflow-ddd
moai-workflow-ddd 是用于对现有代码库进行行为保持型重构的工作流,尤其适合测试覆盖有限的项目。它引导团队分析代码、编写特征测试,并在持续验证中逐步改进结构。
主要功能
- 01分析领域边界与耦合
- 02用特征测试记录现有行为
- 03将快照用作行为契约
- 04通过测试检查逐步重构
- 05使用 AST-grep 分析和转换代码
适用场景
- 01在不改变行为的前提下重构遗留代码
- 02减少生产系统的技术债
- 03在保留契约的同时迁移 API
- 04借助测试安全网改进代码结构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ modu-ai/ moai-adk --skill moai-workflow-ddd安装后在 Claude Code 中输入/moai-workflow-ddd确认已加载。
详细介绍
领域驱动开发(DDD)工作流
开发模式配置(重要)
[备注] 此工作流根据 .moai/config/sections/quality.yaml 进行选择:
constitution:
development_mode: ddd # or tdd
何时使用此工作流:
development_mode: ddd→ 使用 DDD(此工作流)development_mode: tdd→ 改用 TDD(moai-workflow-tdd)
主要区别:
- DDD:针对测试覆盖率较低的现有代码库,优先编写特征测试
- TDD(默认):所有工作均采用测试优先开发,包括在预先进行 RED 分析的棕地项目
快速参考
领域驱动开发为重构现有代码库提供了一种系统化方法,尤其适用于必须保留行为的场景。与用于创建新功能的 TDD 不同,DDD 改进结构而不改变行为。
核心周期——分析-保留-改进:
- 分析:识别领域边界、耦合度量、AST 结构分析
- 保留:特征测试、行为快照、验证测试安全网
- 改进:逐步进行结构变更,并持续验证行为
何时使用 DDD:
- 重构已有测试的遗留代码
- 改进代码结构而不改变功能
- 减少生产系统中的技术债务
- 处理 API 迁移和弃用
- 代码现代化项目
- 绿地项目(采用调整后的周期——见下文)
何时不使用 DDD:
- 需要改变行为时(先修改 SPEC)
- 代码已存在,且目标是改变行为而非进行保留行为的重构时(DDD 会保留行为;如需实现新行为,请先修改 SPEC,或使用 TDD)
绿地项目适配:
对于没有现有代码的新项目,DDD 会调整其周期:
- 分析:使用需求分析代替代码分析
- 保留:通过规范测试定义预期行为(测试优先)
- 改进:实现代码以满足已定义的测试
这使 DDD 成为 TDD 的超集——它既包含 TDD 的测试优先方法,也支持重构场景。
核心理念
DDD 与 TDD 对比
TDD 方法(适用于新功能):
- 周期:RED-GREEN-REFACTOR
- 目标:通过测试创建新功能
- 起点:不存在代码
- 测试类型:定义预期行为的规范测试
- 结果:包含测试覆盖的新工作代码
DDD 方法(适用于重构):
- 周期:分析-保留-改进
- 目标:改进结构而不改变行为
- 起点:行为明确的现有代码
- 测试类型:捕获当前行为的特征测试
- 结果:结构更完善且行为一致的代码
行为保留原则
DDD 的黄金法则是,重构前后的可观察行为必须完全一致。这意味着:
- 所有现有测试必须原样通过
- API 契约保持一致
- 副作用保持一致
- 性能特征保持在可接受范围内
实施指南
阶段 1:分析
分析阶段侧重于了解当前代码库的结构并识别重构机会。
识别领域边界
通过检查以下内容,识别代码库中的逻辑边界:
- 模块依赖和导入模式
- 组件间的数据流
- 共享状态和耦合点
- 公共 API 接口
使用 AST-grep 分析结构模式。对于 Python,搜索导入模式以了解模块依赖。对于类层次结构,分析继承关系和方法分布。
耦合度与内聚度指标
评估代码质量指标:
- 传入耦合度(Ca):依赖此模块的类的数量
- 传出耦合度(Ce):此模块依赖的类的数量
- 不稳定性(I):Ce / (Ca + Ce)——值越高,表示越不稳定
- 抽象度(A):抽象类 / 类总数
- 与主序列的距离:|A + I - 1|
低内聚度和高耦合度表明这些代码可能需要重构。
结构分析模式
使用 AST-grep 识别有问题的模式:
- 方法或职责过多的上帝类
- 方法过度使用其他类数据的特性依恋
- 表明缺少抽象的过长参数列表
- 各模块间的重复代码模式
创建分析报告,记录:
- 当前架构概览
- 已识别的问题区域及严重程度评级
- 拟议的重构目标及风险评估
- 展示耦合关系的依赖关系图
阶段 2:PRESERVE(保留)
保留阶段会在做出任何更改之前建立安全网。
特征测试
特征测试会在不对正确性做任何假设的情况下捕获现有行为。目标是记录代码实际的行为,而不是它应该呈现的行为。
创建特征测试的步骤:
- 步骤 1:通过执行识别关键代码路径
- 步骤 2:创建测试以覆盖这些路径
- 步骤 3:先让测试失败,以发现实际输出
- 步骤 4:更新测试,使其预期结果与实际输出一致
- 步骤 5:记录发现的任何意外行为
特征测试命名约定:testcharacterize[component]_[scenario]
行为快照
对于复杂输出,使用快照测试捕获当前行为:
- API 响应快照
- 序列化输出快照
- 状态转换快照
- 错误消息快照
快照文件在重构期间充当行为契约。
测试安全网验证
进入改进阶段之前,请确认:
- 所有现有测试均通过(100% 通过)
- 新的特征测试覆盖了重构目标
- 受影响区域的代码覆盖率达到阈值
- 安全网中不存在不稳定测试
如果有条件,请运行变异测试以验证测试的有效性。
阶段 3:IMPROVE(改进)
改进阶段会进行结构性更改,同时持续验证行为是否得到保留。
渐进式转换策略
切勿一次性进行大规模更改。遵循以下模式:
- 进行尽可能小的结构性更改
- 运行完整测试套件
- 如果测试失败,立即回退
- 如果测试通过,提交更改
- 重复以上步骤,直到达到重构目标
安全的重构模式
提取方法:当代码块可以命名并独立出来时使用。使用 AST-grep 搜索重复代码块或过长方法,以识别候选项。
提取类:当一个类承担多项职责时使用。在保持原有 API 不变的前提下,将相关方法和字段移至新类,并通过委托维持原有 API。
移动方法:当一个方法使用其他类的数据多于自身数据时使用。移动方法,同时保留所有调用位置。
内联重构:当间接调用增加复杂度却没有带来益处时使用。将委托替换为直接实现。
重命名重构:当名称无法反映当前理解时使用。使用 AST-grep rewrite 原子化地更新所有引用。
AST-grep 辅助转换
使用 AST-grep 进行安全且具备语义感知的转换:
对于方法提取,创建一条规则,用于识别代码模式并将其重写为提取后的形式。
对于 API 迁移,创建一条规则,用于匹配旧 API 调用并将其重写为新 API 格式。
对于弃用处理,创建规则以识别已弃用的模式并建议现代替代方案。
持续验证循环
每次转换后:
- 运行单元测试(快速反馈)
- 运行集成测试(验证行为)
- 运行特征测试(快照对比)
- 确认没有引入新的警告或错误
- 如适用,检查性能基准
DDD 工作流执行
标准 DDD 会话
通过 moai:2-run 以 DDD 模式执行 DDD 时:
步骤 1 - 初始评估:
- 阅读 SPEC 文档,了解重构范围
- 确定受影响的文件和组件
- 评估当前测试覆盖率
步骤 2 - 分析阶段执行:
- 对目标代码运行 AST-grep 分析
- 生成耦合度和内聚度指标
- 创建领域边界图
- 记录重构机会
步骤 3 - 保留阶段执行:
- 验证所有现有测试均通过
- 为未覆盖的路径创建特征测试
- 生成行为快照
- 确认安全网足够完善
步骤 4 - 改进阶段执行:
- 逐步执行转换
- 每次更改后运行测试
- 立即提交成功的更改
- 记录发现的所有问题
步骤 5 - 验证与完成:
- 运行完整测试套件
- 对比更改前后的指标
- 验证所有行为快照均匹配
- 生成重构报告
DDD 循环模式
对于需要多轮迭代的复杂重构:
- 根据范围设置最大循环迭代次数
- 每轮循环聚焦于一个重构目标
- 退出条件:所有目标均已处理或达到迭代次数上限
- 通过更新 TODO 列表跟踪进度
质量指标
DDD 成功标准
行为保留(必需):
- 所有已有测试均通过
- 所有特征测试均通过
- API 契约没有变化
- 性能保持在界限内
结构改进(目标):
- 降低耦合度指标
- 提高内聚度评分
- 降低代码复杂度
- 更好地分离关注点
DDD 专项 TRUST 验证
以 DDD 为重点应用 TRUST 5 框架:
- 可测试性:特征测试覆盖率足够
- 可读性:已验证命名和结构改进
- 可理解性:领域边界更加清晰
- 安全性:没有引入新的漏洞
- 透明度:所有更改均有记录且可追溯
集成点
与 AST-Grep 技能集成
DDD 高度依赖 AST-grep 来实现:
- 结构化代码分析
- 模式识别
- 安全的代码转换
- 多文件重构
规则位于 .moai/config/astgrep-rules/;使用 moai ast-grep 进行扫描,并使用 moai ast-edit 应用重写。
与测试工作流集成
DDD 与测试工作流相辅相成:
- 使用测试模式中的特征测试
- 与变异测试集成,以验证安全网
- 利用快照测试基础设施
与质量框架集成
DDD 的输出可用于质量评估:
- 对比更改前后的指标
- 使用 TRUST 5 验证更改
- 跟踪技术债务
故障排除
常见问题
转换后测试失败:
- 立即还原到最近的已知良好状态
- 分析哪些测试失败以及失败原因
- 检查转换是否意外改变了行为
- 考虑采用更小的转换步骤
特征测试不稳定:
- 找出非确定性的来源
- 模拟外部依赖
- 修复依赖时间或顺序的行为
- 考虑快照容差设置
性能下降:
- 对比更改前后的性能
- 找出受更改影响的热点路径
- 考虑缓存或优化
- 记录可接受的权衡
恢复流程
DDD 会话遇到问题时:
- 使用 git stash 保存当前状态
- 重置到最近一次成功提交
- 检查导致失败的转换
- 规划替代方案
- 从已保存的状态继续
版本:1.0.0 状态:Active
常见的自我辩解
| 自我辩解 | 实际情况 |
|---|---|
| “这段遗留代码显然有问题,我直接重写就行” | 看起来有问题的代码可能承载着调用方依赖的生产环境行为。先进行分析,再编写测试后重写。 |
| “特征测试只是白费功夫” | 特征测试是安全网,让你能放心重构。没有它们,重构就等于重写。 |
| “我足够了解这个模块,可以跳过分析” | 理解不等于验证。分析会促使你明确假设,并审视调用方。 |
| “这个函数没有测试,因为它没有值得测试的行为” | 只要有调用方,就有契约。需要刻画的正是这个契约。 |
| “我会小心处理,以保留原有行为” | 小心不是一种保障机制。只有测试能捕获回归问题。 |
| “这次重构太小,不需要 DDD” | 小规模重构恰恰会因人们跳过安全网而造成悄无声息的回归。 |
切斯特顿围栏原则:不要移除或替换你无法解释其用途的代码。设置分析阶段,正是为了找出那些看起来有问题的代码背后的原因。
危险信号
- 重构提交没有新增测试,也没有修改任何现有测试
- 缺少分析产物,或分析产物被缩减为一句话总结
- 特征测试只断言返回值,忽略副作用和状态变化
- 改进阶段引入了没有测试覆盖的新公共 API
- 声称保留了原有行为,却没有提供重构前后的测试证据
- 改进阶段删除了遗留测试,而不是更新它们
验证
- 分析产物存在,并列出所有调用修改后代码的调用方
- 改进开始前,所有公共入口点都已有特征测试
- 特征测试在重构前的提交上通过(已验证基线)
- 每个改进步骤完成后,特征测试仍然通过
- 没有任何现有测试在未提供等效替代的情况下被删除
- 修改后软件包的覆盖率不低于变更前
- 至少有一条测试断言能捕获副作用(I/O、全局状态)