📖 はじめに — 第I部の「読み方」を先に宣言する
序章は「LLM コーディングは増幅であり、リファクタリングしなければ技術的負債が増幅されるだけ」と論じた。第I部(本章〜第10章)はその実践 — CTS-EC(EC サイト構築・.NET + フロントエンド + Python のモノレポ)で実際に行った負債ドメイン別の一掃の実録である。
各章はドメインこそ違うが、読み進めると同じ型が 4 つ、繰り返し現れることに気づくはずだ。先に宣言しておく。
- default-deny + allowlist 全量列挙 + 件数 pin — 「原則禁止・例外は理由つきで全件列挙・件数を固定して段階解消」。負債を「減った気がする」でなく数で管理する
- 機械ガードのゼロベースライン確認が兄弟負債を炙り出す — ガードを敷くために現状を全量調査すると、認知していなかった同型の負債が必ず出てくる
- 定量が起票を正当化する — 感覚でなく数字(行数・件数・率)で負債を可視化してから意思決定する
- 過剰設計を実態調査で捨てる — 賢い設計案より、実コード・実データの確認が正しい答えを出す
本章の題材は、この型の原点になったバックエンド総リファクタだ。
🧨 負債の姿 — god class 5 件と、境界のない BC
本番リリース前の時点で、バックエンドには 3 種類の負債が積もっていた。
- god class 5 件: 行数にして 1,545 / 721 / 714 / 633 / 467。最大の
ProductsControllerは 1,547 行に達し、ルーティングとビジネスロジックと永続化が同居 - BC(境界づけられたコンテキスト)越境: あるコンテキストのハンドラが別コンテキストのドメイン型やセッションを直接 inject。「境界」は図の上にしか存在しない
- 互換維持コード: 過去の移行を楽にするための global using・namespace の据え置き・型転送が各所に残置
いずれも AI コーディングが加速させた種類の負債だ。AI は「動く最短経路」を選ぶので、既存の god class には追記し、便利な型には直接依存する。序章の増幅論そのものである。
🔥 決定 1: 互換コードを一切残さない — 「本番前が最後のチャンス」
最初の決定が、その後のすべての基調を決めた。
| 案 | 内容 | 帰結 |
|---|---|---|
| A | 互換 stub / 型転送を残す | 段階移行は楽だが、本番後に剥がすコストは指数的に増える |
| B | 完全削除(採用) | 差分は大きいが、負債ゼロでリリースを迎える |
| C | 一部だけ剥がす | 中途半端で、結局 B と同じ手戻り |
判断の軸は「今しかできないことは何か」だ。本番運用が始まれば、後方互換を求めるクライアントが生まれ、DB には守るべきデータが積もり、互換コードは「剥がせない前提」に変わる。本番前の今だけは、互換ゼロの完全削除が選べる — この認識を決定ログに明文化し、約 150 ファイルの差分を Wave 単位の MR に分割して実行した。
🪓 決定 2: god class は「200 行物理ゲート」で解体する
god class 分解で難しいのは、分解の基準が人によってぶれることだ。採用した標準は 2 つ。
- Controller は 200 行未満を物理ゲートとする — 200 行に収まるのは「ルーティング + DTO バインドのみ」の状態であることを、実コードの読み取りで確認して基準化した
- 分解パターンは Coordinator + Strategy に統一 — 分岐の増殖は Strategy に逃がし、手順の束ねは Coordinator が持つ。パターンを 1 つに固定するとレビューが速くなる
ProductsController(1,547 行)は 4 Controller × 4 Service に分解された。ここで見逃せないのがテストの扱いだ。旧クラスに直接依存したテストが 4 ファイル・4,564 行あったが、これらは「実装の形」に結合したテストであり、分解後は検証対象ごと消滅する。純粋関数として残る部分だけをヘルパーテスト(23 ケース)に移行し、残りは削除した。
テストも負債になる。 実装の内部構造に張り付いたテストは、リファクタの妨げにしかならない。4,564 行の削除を「カバレッジ低下」と怯えるのではなく、「実装都合のテストを仕様ベースのテストに置き換える機会」と捉える。この整理は第7章(テスト品質編)の伏線になる。
🧱 決定 3: BC 越境は「Read 専用 Port + DTO」で断つ
BC 越境の解消では、Port(境界インターフェース)の設計が本丸だった。
- 却下案: Read + Write 両用の Port(
GetAsync+SaveAsync)。API に統一感は出るが、BC 越境を抽象化レイヤーで再現するだけで、境界は結局守られない - 採用案: Port は Read 専用 + DTO 返却に限定。書込は SAGA / メッセージ経由で持ち主 BC のハンドラに委譲する(この書込経路の設計が第4章の主題になる)
ドメインオブジェクトの直接返却も禁止し、Adapter 内で DTO 変換を強制した。「参照はいいが、他人の家の家具は動かすな。動かしたければ持ち主に頼め」— 境界の定義がこの 1 行に還元されたことで、以後の判断が機械化できるようになった。
Before / After を並べるとこうなる。

そして境界はアーキテクチャテストで default-deny 固定する。各 BC に越境テスト(Reflection ベース・対象アセンブリを名前でロードして依存を走査)を敷き、越境は原則 FAIL、正当な例外だけを allowlist に理由つきで列挙する。
🗣️ 「allowlist で逃がすな」— 設計を変えたオーナーの一言
ここで、本章いちばんの実録がある。TenantCatalog コンテキストに残っていた越境 5 件について、当初の実装案は「越境テストの allowlist に載せて追跡する」だった。ガードは敷かれるし、負債は可視化される。悪くない案に見える。
オーナーの判定は違った — 「技術的負債解消が目的。allowlist で逃がすな」。
方針は転換され、5 件すべてを根本解消(新規 Read Port 5 本 + 既存 Port 拡張 3 本)して allowlist は空配列になった。この往復が教えてくれるのは、型 1(default-deny + allowlist)の危うい側面だ。allowlist は「例外を管理する仕組み」であって「解消を先送りする仕組み」ではない。可視化した瞬間に満足する誘惑は、機械ガードを敷く側に常にある。
ただし、何でもゼロにすればいいわけではない
対照的な判断も同じステアリングにある。Invitation コンテキストの越境を実コードで裏取りすると、Read と Write の 2 系統が混ざっていた。Read は Port 化で解消。しかし Write 側(テナント受入処理)は、複数集約への書込 + 外部 ID 基盤の補償処理を含む実質 SAGA であり、Read Port で置換できる代物ではない。
これを無理に本ステアリングへ押し込めば、テナント作成という本番の根幹処理を「ついでに」設計することになる。判断は allowlist に明示 + 技術的負債として起票し、専用ステアリングへ分離。完了ゲートも「全越境ゼロ」から「Read 越境ゼロ + Write 越境は識別・追跡下にある」へ言い換えた。
「逃がすな」と「分離しろ」は矛盾しない。根本解消できるものは今やる。できないものは、できない理由ごと台帳に載せて追跡する。ゼロか放置かの二択にしないのが、返済ループを回し続けるコツだ。
🔬 型 4 の実例 — MallType 昇格は「ワイヤ形式の裏取り」から
共有すべき enum(MallType)が特定 BC のドメインに置かれ、越境の温床になっていた。SharedKernel への昇格は自然な解だが、参照 337 件の機械置換と、もっと怖い問題 — 永続化済みデータとの互換 — が立ちはだかる。
ここでの手順が型 4(実態調査)の見本だった。
- ドキュメント DB(Marten)の enum 保存形式を公式ドキュメントで確認 → 既定は 整数値で保存され、CLR の namespace には依存しない
- 実コードで serializer 設定を確認 → enum 設定は既定のまま = 整数保存が確定
- 結論: namespace を移動してもワイヤ上のデータは 1 バイトも変わらない。後方互換を確証してから昇格を実行
実は途中段階では「越境回避のため cross-BC メッセージを string 化する」案も採られていた。だが昇格によって「string 化の根拠そのものが消滅」したため、enum 直接受け渡しへ再転換している。判断は前提が変われば巻き戻す — 決定ログに却下案と根拠を残しているから、この巻き戻しが安全にできた。
🧾 まとめ
- 互換維持コードは「本番前が最後のチャンス」で完全削除。剥がすコストは本番後に指数化する
- god class は 200 行物理ゲート + Coordinator/Strategy の標準パターンで解体。実装都合の旧テスト 4,564 行は「負債」として削除し、仕様ベースへ移行
- BC 境界は Read 専用 Port + DTO で定義し、書込は SAGA / メッセージで持ち主に委譲。default-deny の越境テストで固定
- 「allowlist で逃がすな」 — 根本解消できるものは今やる。できないもの(Write 越境 = 実質 SAGA)は理由ごと起票して分離する
- 型を動かす前にワイヤ形式を裏取りする。前提が変われば判断は巻き戻す
次章は、この章が持ち主に委譲した「書込経路」の物語 — SAGA という新アーキテクチャに挑戦し、発散し、契約と再発ゲートで固定するまでのSAGA 編。
📚 シリーズ記事(ステアリング駆動開発・実践編)
序章
第I部: 技術的負債ドメイン別の実録(総点検ガイド + 8 ドメイン・全 9 回)
- リファクタリング総点検ガイド — 7 つの観点と進め方
- DDD/SOLID/BC 編(本記事)
- SAGA 編 — 新アーキテクチャ挑戦と再発ゲート
- Atomic Design リファクタ編 — 47 page 新規移植
- Storybook × a11y 実機編
- テスト品質・網羅性編
- Python ジョブ群編
- パフォーマンス編
- セキュリティ編
第II部: 検証エンジン(クロスファミリー検証・全 6 回)
- 総論 — マルチ LLM の 2 系統と見取り図
- 裏取り編 — +18.1pt 論文の検証
- 設計編 — finder/verifier 分業と逆順禁止
- TDD×ハーネス編 — テスト保護と三層ゲート
- 実装編 — Claude Code の中から Codex を動かす
- モデル戦略編 — ティア割当と「買うか組むか」
終章
- クロスファミリー検証の最終型 —「正しく作る」から「自分で正しさを測り直す」へ — 検証エンジンに「計測」の層をはめ、自己修正ループを閉じる
番外編
- CI テスト 29 分 → 5.6 分 — 検証エンジンの平時運用実録 — 実測が有力仮説を殺し、退行前より速くなった 1 日
- GPT-5.6 sol 切替の当日実録 — もう一つのデフォルト追従 — CLI 更新が黙って替える finder と、当日中の再計測
- 遊休 90% の枠に仕事を振る — Codex 上流調査と verifier バッチ化 — 逆順禁止の境界を ADR で確定し、読む仕事を遊休枠へ移した 1 日
- ハーネスが単一プロジェクトを卒業する — プラグイン化と二層配布 — 4 プロジェクト展開への切り出しと、version + SHA ピンによる非強制追従
- 69分で5リリース — Opus 5 当日対応が暴いた共通ハーネスの死角 — 新モデル対応を起点に、配布・文書・CI の回帰を二消費者で検出した 69 分
関連記事
- DDD(ドメイン駆動設計) / SOLID 原則 — 理論編
- CQRS — Read/Write 分離の背景概念