CTS-KB

Atomic Design リファクタ編 — 47 page を「新規移植アプローチ」で 5 日で分離し、負債 43,500 行を消すまで【実践編 第5章】

⏱ 約 10 分で読めます
#ステアリング駆動開発#Atomic Design#Angular#リファクタリング#技術的負債#Smart/Dumb分離#Nx#フロントエンド

📖 はじめに — 理論は知っている。では「動いている 45 page」をどうするか

開発手法ガイドの Atomic Design は分類の理論を扱った。本章はその続きではなく、理論どおりに作れなかった現実を、動いているアプリのまま作り直した実録である。

CTS-EC のフロントエンド(Angular + Nx モノレポ)は、Smart/Dumb 分離の参照実装が home ページ 1 件だけ存在し、残り 45 page は未適用という状態だった。ページコンポーネントに DI・表示ロジック・HTML が同居し、Storybook 検収もできない。序章で述べたとおり、AI コーディングはこの種の「動くが構造が崩れたコード」を高速に量産する — そして放置すれば、この上にさらに 180 page が増築される予定だった。

フロントエンドの完成度はまだ 20%。つまり「本番前の今が、レールを敷き直す最後のチャンス」だった。

⚖️ 決定 1: 全 45 page を分離する(部分適用の誘惑を断つ)

最初の分岐は「どこまでやるか」だ。検討した 4 案を、ステアリングの決定ログからそのまま示す。

内容コスト効果
A全 45 page を分離(採用)約 406〜657h負債ゼロ
B重要 page のみ(5〜8 件)分離 + 規約緩和約 100〜200h部分的解消
Chome を戻す(規約撤退)約 1hStorybook 検収の放棄
D新規 page のみ分離を強制仕組みのみ既存 45 page の負債は残る

案 D は一見合理的だ。「触るときに直す」は現場の定番でもある。だが 180 page 増を控えた時点でこれを選ぶと、規約に「例外ゾーン」が恒久化する。新規実装者(人間も AI も)は例外ゾーンを見て学習するので、レールは敷いた瞬間から侵食される。バックエンドで先行した負債一掃(BC 境界の機械ガード・第3章)と同じ思想で、例外ゼロの状態を作ってから機械ガードで固定する案 A を選んだ。

💡 決定 2: 「リファクタしない」— 新規移植アプローチ

本章の最大の輸出品がこれだ。既存 page を分離パターンへ「リファクタ」する案と比較した結果:

アプローチ工数(45 page)リスク
A既存ファイルを編集して分離548〜938h高(責務再判定・テスト分割・HTML イベント全書換)
B退避 → generator → 移植(採用)336〜552h(約 40% 減)低(並行稼働・ロールバック容易)
💡 決定 2: 「リファクタしない」— 新規移植アプローチ

既存 page を.legacy/ へ git mv

Nx generator@cts-ec/page で雛形生成

旧コードを見ながら移植(旧 spec → 新 spec = Red、実装で Green)

grep 機械検証+ テスト全緑

.legacy/ 削除

なぜリファクタより「作り直し」が安く、安全なのか。

  • HTML イベント binding の全置換が不要 — 最初から output で書くので、既存 (click)="service.save()" を一つずつ剥がす作業が消える
  • spec 分割の頭の体操が不要 — 最初から container / presentational 別ファイルでテストを書く
  • 旧コードが .legacy/ にそのまま残る — 移植中も並行稼働でき、参照元(写経元)として常に手元にあり、ミスってもロールバックは git mv を戻すだけ
  • TDD サイクルが綺麗 — 旧 spec を新 spec に移植した時点で Red、実装移植で Green。「リファクタ」ではなく「写経 with 参照」になる

「動いているコードを変形する」から「動いているコードを答えとして横に置き、正しい構造で書き直す」への転換。これは AI 実装との相性も良い — AI にとって「既存の絡まった責務を保ったまま分解する」より「雛形 + 参照実装 + 写経元」の方が、圧倒的に失敗しにくいタスクだからだ。

🏗️ 到達点の構造 — Container は配線だけ、HTML は libs/ui だけ

移植後の到達点がこの構造だ(これを固定する機械ゲートは次節)。

Atomic Design × Smart/Dumb 分離 — 到達点の構造

  • Container(apps 側): DI・Route・配線のみ。HTML/CSS を持たず、inline template は <ui-{name}-page [input]="..." (output)="..." /> の 1 行
  • Presentational(libs/ui 側): HTML/CSS を所有し、input/output のみで外界と接続。DI なし。だから Storybook でそのまま検収できる
  • 命名はサフィックスなしの home パターン(home.ts / HomeComponent / app-home)。配置場所で責務が分かるため、-container サフィックスは認知負荷にしかならない、という判断

雛形は Nx generator @cts-ec/page が生成する(presentational 5 ファイル + container 2 ファイル + barrel 更新)。手作業なら 1 page あたり 1〜2h かかるボイラープレートが消え、45 page で 45〜90h の節約。それ以上に大きいのは、命名規則違反や import 漏れという「雛形のミス」が構造的に起きなくなることだ。180 page の増築を人間と AI が混在で進める以上、規約は generator に焼き込まないと形骸化する。

🔒 完了基準は grep — 「分離できた気がする」を排除する

「負債ゼロ」を主観レビューで判定すると、ゼロは維持できない。完了基準はすべて grep / find の機械検証にした。

ゲート検証完了値
AT-1grep -rln "inject(.*Service|inject(.*Store" apps/pages/0
AT-7find frontend -name "*.component.ts"(Angular 旧命名)0
AT-8find frontend -name "*.directive.ts"0
AT-10find frontend/apps -name "*.service.ts"0
AT-18ダークモード違反([class.X] の dark ペア欠落をファイル単位で検出)0

実行は CI + pre-push hook の二段。CI だけだと「push してから落ちる」ラウンドトリップが発生するので、ローカルの pre-push で先に止める。バックエンドのアーキテクチャテストと同思想の、フロントエンド版決定論ゲートである。

細部の学びも 1 つ。AT-18(ダークモード)は当初「同一行 grep」で書いたが、Angular の property binding は属性が複数行に割れるため誤検出が出た。ファイル単位で dark ペアの存在を検査する方式に切り替えて安定した。機械ゲートは書いて終わりではなく、誤検出と戦って初めて「常時 enforce」できる。

📊 結果 — 5 日間・134 タスク・正味 −12,000 行

項目
移植完了 page47(当初 45 + 分割で増えた分)
期間 / タスク5 日間 / 134 タスク・コミット 16 件
削除した legacy118 ファイル / 約 43,500 行
正味の行数増減−12,000 行(追加 約 35,000 / 削除 約 47,000)
テスト約 4,800 → 5,462 件 PASS(+662)
CI ゲート5 → 9 ゲート常時 enforce
旧命名(.component.ts ほか)全廃(0 件)

「作り直したのに正味 −12,000 行」が新規移植アプローチの成績表だ。リファクタで同じ到達点に行くより 4 割安く、しかもテストは増え、ゲートは倍増している。

面白い副次効果もあった。旧命名一掃フェーズ(17 タスク)に着手したら、9 件はすでに片付いていた — 各 page の移植時に generator が新命名で生成するので、自然に消化されていたのだ。レールを先に敷くと、負債の一部は「意識して返す」前に消える。

🧠 移植で得た教訓 — 巨大 page と State 二重保持

順風満帆ではない。実録として重要なのはむしろここだ。

巨大 page は「事前分解設計」なしで触らない

最難関は product-detail1,569 行・297 テスト)と staff-list(804 行・2 つの管理者 view を内包)。これらは移植前に専用の設計セッション(計 9 件)で input/output 設計を確定してから着手した。結果、移植中の手戻りはほぼゼロ。「巨大なものは、動かす前に紙の上で割る」 — 写経方式でも、写経元が絡まっていれば設計は必要だ。staff-list は責務の異なる 2 page への分割で各 400 行前後に落とした。

State 二重保持 — 最終レビューで見つかった設計バグ

完了直前の並列レビュー(3 エージェント)で重大な問題が 2 件出た。

  1. product-detail: Container が 21 個の linkedSignal を独立保持していたが、Presentational から変更通知の output が出ておらず、未保存変更ガードが実質機能していなかった。Presentational に hasChangesChange output を追加して根治
  2. store-api-settings: 「Dialog 状態は Presentational 内部完結」と設計書に明記したのに、Container 側にも同名 signal が並立していた。save output に必要データを全部載せて一本化

教訓は明快で、「Container と Presentational に同名 signal があったら設計バグ」。分離パターンの落とし穴は DI 混入のような分かりやすい違反ではなく、この「両側で同じ状態を持ってしまう」静かな二重化にある。以後、レビューチェックリストに恒久追加した。

as unknown as キャストは設計矛盾のシグナル

移植中に型キャストが 8 箇所蓄積していた。Store のメソッドシグネチャ不整合を「とりあえずキャストで通す」で放置した結果だ。振り返りの結論は「キャストを書いた瞬間が、設計矛盾に気づいた瞬間。その場で直すか、直せないなら起票する」。AI 実装は型エラーをキャストで黙らせる誘惑に強くない。だからこれも人の規律でなくレビュー観点として明文化した。

🧹 続編: 部品階層の「純化」— 60 部品を参照数で仕分ける

47 page の分離が終わると、次の負債が見えた。分離の過程で Dumb 側の部品が organisms に流れ込み、「1 つの page からしか参照されない部品」が部品階層を汚染していたのだ。

続編ステアリングでは、全 atoms / molecules / organisms 60 件の参照元 page 数を機械集計した(selector を grep で抽出し、参照 page 数を数えるだけ — ここでも定量が先)。

階層参照 0 件1 件2 件以上合計
atoms61613
molecules72312
organisms1518235
合計28211160

60 部品のうち、2 page 以上から参照される「本物の再利用部品」は 11 件しかなかった。そこでルールを反転させた — 「部品階層に置けるのは 2 page 以上から参照されるものだけ」。1 page 専用の部品は pages/{name}/parts/ へ移送し、atoms/molecules へのドメイン語混入(mall-category-row など 3 件)も禁止。それぞれ AT-19 / AT-20 として grep ゲート化した。

Atomic Design の分類論争(これは molecule か organism か)は不毛になりがちだが、「参照 page 数」という機械的な基準に置き換えると、議論は集計に変わる。これがこの続編の一番の発明だと思う。

🧾 まとめ — 第I部の「型」がここにも

  • 動いている 45+ page の構造改革は、リファクタでなく新規移植(退避 → generator → 写経) が安くて安全 — 工数 40% 減・正味 −12,000 行
  • 完了基準は grep / find の機械検証 9 ゲートで固定し、CI + pre-push で常時 enforce
  • 巨大 page は事前分解設計、同名 signal の二重保持はレビュー観点に、キャストは設計矛盾のシグナル
  • 部品階層は「2 page 以上参照」の数値基準で純化 — 分類論争を集計に変える

序章の言葉に戻れば、LLM コーディングは増幅だ。この章で敷いたレール(generator + 9 ゲート)の上では、180 page の増築を AI に任せても Atomic Design は崩れない — 崩れたら push が止まる。次章は同じフロントエンドの品質を「実機」で検証する話、Storybook × a11y 実機編へ。

📚 シリーズ記事(ステアリング駆動開発・実践編)

序章

  1. AI 駆動開発が積み上げる技術的負債

第I部: 技術的負債ドメイン別の実録(総点検ガイド + 8 ドメイン・全 9 回)

  1. リファクタリング総点検ガイド — 7 つの観点と進め方
  2. DDD/SOLID/BC 編 — god class 一掃と境界の機械ガード
  3. SAGA 編 — 新アーキテクチャ挑戦と再発ゲート
  4. Atomic Design リファクタ編(本記事)
  5. Storybook × a11y 実機編
  6. テスト品質・網羅性編
  7. Python ジョブ群編
  8. パフォーマンス編
  9. セキュリティ編

第II部: 検証エンジン(クロスファミリー検証・全 6 回)

  1. 総論 — マルチ LLM の 2 系統と見取り図
  2. 裏取り編 — +18.1pt 論文の検証
  3. 設計編 — finder/verifier 分業と逆順禁止
  4. TDD×ハーネス編 — テスト保護と三層ゲート
  5. 実装編 — Claude Code の中から Codex を動かす
  6. モデル戦略編 — ティア割当と「買うか組むか」

終章

  1. クロスファミリー検証の最終型 —「正しく作る」から「自分で正しさを測り直す」へ — 検証エンジンに「計測」の層をはめ、自己修正ループを閉じる

番外編

  1. CI テスト 29 分 → 5.6 分 — 検証エンジンの平時運用実録 — 実測が有力仮説を殺し、退行前より速くなった 1 日
  2. GPT-5.6 sol 切替の当日実録 — もう一つのデフォルト追従 — CLI 更新が黙って替える finder と、当日中の再計測
  3. 遊休 90% の枠に仕事を振る — Codex 上流調査と verifier バッチ化 — 逆順禁止の境界を ADR で確定し、読む仕事を遊休枠へ移した 1 日
  4. ハーネスが単一プロジェクトを卒業する — プラグイン化と二層配布 — 4 プロジェクト展開への切り出しと、version + SHA ピンによる非強制追従
  5. 69分で5リリース — Opus 5 当日対応が暴いた共通ハーネスの死角 — 新モデル対応を起点に、配布・文書・CI の回帰を二消費者で検出した 69 分

関連記事