moai-workflow-ddd について
moai-workflow-dddは、既存コードの動作を維持しながらリファクタリングするワークフローです。特にテストカバレッジが限られるプロジェクトを対象に、分析、特性テスト、継続的な検証を伴う段階的な構造改善を導きます。
主な機能
- 01ドメイン境界と結合度の分析
- 02特性テストによる既存動作の記録
- 03動作契約としてのスナップショット
- 04テストで確認しながら段階的にリファクタリング
- 05AST-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)ワークフロー
開発モードの設定(重要)
[NOTE] このワークフローは .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 は動作を変更せずに構造を改善します。
コアサイクル - ANALYZE-PRESERVE-IMPROVE:
- ANALYZE:ドメイン境界の特定、結合度の測定、AST 構造分析
- PRESERVE:特性テスト、動作スナップショット、テストセーフティネットの検証
- IMPROVE:動作を継続的に検証しながら、段階的に構造を変更
DDD を使用する場合:
- 既存のテストがあるレガシーコードのリファクタリング
- 機能を変更せずにコード構造を改善する場合
- 本番システムの技術的負債を削減する場合
- API の移行および非推奨化への対応
- コードのモダナイゼーションプロジェクト
- グリーンフィールドプロジェクト(サイクルを調整して使用。下記を参照)
DDD を使用しない場合:
- 動作の変更が必要な場合(最初に SPEC を変更してください)
- コードがすでに存在し、動作を維持するリファクタリングではなく、動作の変更が目的の場合(DDD は動作を維持します。新しい動作を追加する場合は、最初に SPEC を変更するか、TDD を使用してください)
グリーンフィールドプロジェクトへの適用:
既存のコードがない新しいプロジェクトでは、DDD のサイクルを次のように調整します。
- ANALYZE:コード分析の代わりに要件を分析
- PRESERVE:仕様テストで意図する動作を定義(テストファースト)
- IMPROVE:定義したテストを満たすコードを実装
これにより、DDD は TDD のテストファーストアプローチを取り入れながら、リファクタリングのシナリオにも対応する、TDD の上位集合となります。
基本理念
DDD と TDD の比較
TDD のアプローチ(新機能向け):
- サイクル:RED-GREEN-REFACTOR
- 目標:テストを通じて新しい機能を作成する
- 開始点:コードが存在しない状態
- テストの種類:期待される動作を定義する仕様テスト
- 結果:テストカバレッジを備えた、新たに動作するコード
DDD のアプローチ(リファクタリング向け):
- サイクル:ANALYZE-PRESERVE-IMPROVE
- 目標:動作を変更せずに構造を改善する
- 開始点:動作が定義された既存のコード
- テストの種類:現在の動作を記録する特性テスト
- 結果:同一の動作を維持しながら、構造を改善したコード
動作維持の原則
DDD の黄金律は、リファクタリングの前後で観測可能な動作が同一であることです。これは次のことを意味します。
- 既存のテストがすべて変更なしで合格する
- API コントラクトが同一に保たれる
- 副作用が同一に保たれる
- パフォーマンス特性が許容範囲内に保たれる
実装ガイド
フェーズ 1:ANALYZE
分析フェーズでは、現在のコードベースの構造を理解し、リファクタリングの機会を特定します。
ドメイン境界の特定
次の項目を調べ、コードベース内の論理的な境界を特定します。
- モジュールの依存関係とインポートパターン
- コンポーネント間のデータフロー
- 共有状態と結合点
- パブリック API の範囲
AST-grep を使用して、構造パターンを分析します。Python では、インポートパターンを検索してモジュールの依存関係を把握します。クラス階層については、継承関係とメソッドの分布を分析します。
結合度と凝集度のメトリクス
コード品質メトリクスを評価します。
- 求心性結合度(Ca):このモジュールに依存するクラスの数
- 遠心性結合度(Ce):このモジュールが依存するクラスの数
- 不安定度(I):Ce / (Ca + Ce) — 値が高いほど安定性が低くなります
- 抽象度(A):抽象クラス数 / クラス総数
- 主系列からの距離:|A + I - 1|
凝集度が低く、結合度が高い場合は、リファクタリング候補です。
構造分析のパターン
AST-grep を使用して問題のあるパターンを特定します。
- メソッドや責務が多すぎるゴッドクラス
- メソッドが別のクラスのデータを過剰に使用するフィーチャーエンビー
- 抽象化の不足を示す長いパラメーターリスト
- モジュール間で重複しているコードパターン
分析レポートを作成し、以下を記録します。
- 現在のアーキテクチャの概要
- 重大度評価を付けた特定済みの問題領域
- リスク評価を含むリファクタリング対象の提案
- 結合関係を示す依存関係グラフ
フェーズ 2:PRESERVE
preserve フェーズでは、変更を加える前にセーフティネットを確立します。
特性テスト
特性テストは、正しさを仮定せずに既存の動作を捉えます。目的は、コードがどう動作すべきかではなく、実際にどう動作するかを記録することです。
特性テストの作成手順:
- ステップ 1:実行を通じて重要なコードパスを特定します
- ステップ 2:これらのパスを実行するテストを作成します
- ステップ 3:まずテストを失敗させ、実際の出力を明らかにします
- ステップ 4:実際の出力を期待するようにテストを更新します
- ステップ 5:発見した予想外の動作を記録します
特性テストの命名規則:testcharacterize[component]_[scenario]
動作スナップショット
複雑な出力には、スナップショットテストを使って現在の動作を記録します。
- API 応答のスナップショット
- シリアライズ出力のスナップショット
- 状態変換のスナップショット
- エラーメッセージのスナップショット
スナップショットファイルは、リファクタリング中の動作契約として機能します。
テストセーフティネットの検証
改善フェーズに進む前に、以下を確認します。
- 既存のテストがすべて成功する(100% 成功)
- 新しい特性テストがリファクタリング対象をカバーしている
- 影響を受ける領域のコードカバレッジがしきい値を満たしている
- セーフティネットに不安定なテストがない
利用可能な場合は、ミューテーションテストを実行してテストの有効性を検証します。
フェーズ 3:IMPROVE
improve フェーズでは、動作が維持されていることを継続的に検証しながら、構造を変更します。
段階的な変換戦略
大規模な変更を一度に行ってはいけません。以下のパターンに従います。
- 可能な限り小さな構造変更を行います
- テストスイート全体を実行します
- テストが失敗したら、直ちに変更を元に戻します
- テストが成功したら、変更をコミットします
- リファクタリングの目標を達成するまで繰り返します
安全なリファクタリングパターン
メソッドの抽出:コードブロックに名前を付けて切り出せる場合に使用します。AST-grep で、繰り返し使われているコードブロックや長いメソッドを検索し、候補を特定します。
クラスの抽出:クラスに複数の責務がある場合に使用します。委譲によって元の API を維持しながら、関連するメソッドとフィールドを新しいクラスに移動します。
メソッドの移動:メソッドが自身のクラスよりも別のクラスのデータを多く使用している場合に使用します。すべての呼び出し箇所を維持しながら移動します。
インラインリファクタリング:間接化が利点なく複雑さを増している場合に使用します。委譲を直接の実装に置き換えます。
名前変更リファクタリング:名前が現在の理解を反映していない場合に使用します。AST-grep の書き換え機能を使って、すべての参照をまとめて更新します。
AST-Grep を活用した変換
安全でセマンティクスを考慮した変換には、AST-grep を使用します。
メソッドの抽出では、コードパターンを特定し、抽出後の形式に書き換えるルールを作成します。
API の移行では、古い API 呼び出しに一致し、新しい API 形式に書き換えるルールを作成します。
非推奨への対応では、非推奨のパターンを特定し、最新の代替手段を提案するルールを作成します。
継続的な検証ループ
各変換の後に、以下を実行します。
- 単体テストを実行します(迅速なフィードバック)
- 統合テストを実行します(動作の検証)
- 特性テストを実行します(スナップショットの比較)
- 新たな警告やエラーが発生していないことを確認します
- 該当する場合は、パフォーマンスベンチマークを確認します
DDD ワークフローの実行
標準的な DDD セッション
DDD モードで moai:2-run を通じて DDD を実行する場合:
ステップ 1 - 初期評価:
- リファクタリングの範囲について SPEC ドキュメントを読みます
- 影響を受けるファイルとコンポーネントを特定します
- 現在のテストカバレッジを評価します
ステップ 2 - 分析フェーズの実行:
- 対象コードに対して AST-grep 分析を実行します
- 結合度と凝集度のメトリクスを生成します
- ドメイン境界マップを作成します
- リファクタリングの機会を文書化します
ステップ 3 - 保護フェーズの実行:
- 既存のテストがすべて合格することを確認します
- カバーされていないパスの特性テストを作成します
- 動作スナップショットを生成します
- セーフティネットが十分であることを確認します
ステップ 4 - 改善フェーズの実行:
- 変換を段階的に実行します
- 変更のたびにテストを実行します
- 成功した変更は直ちにコミットします
- 発見した問題を文書化します
ステップ 5 - 検証と完了:
- テストスイート全体を実行します
- 変更前後のメトリクスを比較します
- すべての動作スナップショットが一致することを確認します
- リファクタリングレポートを生成します
DDD ループパターン
複数回の反復が必要な複雑なリファクタリングの場合:
- 範囲に基づいてループの最大反復回数を設定します
- 各ループでは、1 つのリファクタリング対象に集中します
- 終了条件:すべての対象への対応が完了するか、反復回数の上限に達した場合
- 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 ステータス:有効
よくある自己正当化
| 自己正当化 | 実態 |
|---|---|
| 「このレガシーコードは明らかに壊れているので、書き直せばいい」 | 壊れているように見えるコードには、呼び出し元が依存している本番環境での動作が組み込まれている可能性があります。まず ANALYZE を行い、テストを追加してから書き直してください。 |
| 「特性テストは単なる無駄な作業だ」 | 特性テストは、安心してリファクタリングするための安全網です。これがなければ、リファクタリングは書き直しと同じです。 |
| 「このモジュールは十分に理解しているので、ANALYZE は省略できる」 | 理解していることと、検証済みであることは別です。ANALYZE によって、前提を明示し、呼び出し元と向き合うことができます。 |
| 「この関数にはテストする価値のある動作がないから、テストもない」 | 呼び出し元があるなら、契約があります。その契約こそ、特性を明らかにすべき対象です。 |
| 「慎重に進めれば、動作を維持できる」 | 慎重さは仕組みではありません。リグレッションを検出できる唯一の仕組みはテストです。 |
| 「このリファクタリングは小規模なので、DDD は必要ない」 | 小規模なリファクタリングでも、安全網を省くことで、気づかないリグレッションが発生します。 |
チェスタトンの柵:目的を説明できないコードを削除したり、置き換えたりしてはいけません。ANALYZE フェーズは、間違っているように見えるコードの背後にある理由を明らかにするためにあります。
危険信号
- 新しいテストも既存テストの変更もゼロのリファクタリングコミット
- ANALYZE の成果物がない、または一行の要約に縮小されている
- 副作用や状態変化を無視し、戻り値しか検証しない特性テスト
- IMPROVE フェーズで、テストによるカバレッジのない新しい公開 API が導入される
- リファクタリング前後のテスト結果による証拠なしに、動作を維持したと主張する
- IMPROVE 中に既存テストを更新せず削除する
検証
- ANALYZE の成果物があり、変更対象コードのすべての呼び出し元が記載されている
- IMPROVE を開始する前に、すべての公開エントリーポイントについて特性テストがある
- リファクタリング前のコミットで特性テストが成功する(ベースラインを確認済み)
- IMPROVE の各ステップ後も特性テストが成功する
- 既存テストを削除する場合、同等のテストに置き換えられている
- 変更対象パッケージのカバレッジが、変更前と同等以上である
- 副作用(I/O、グローバル状態)が、少なくとも1つのテストアサーションで捕捉されている