/note/tech

DRYにしすぎるとコードは干からびる

要約:

■ 1. 前置きと問題意識

  • 著者の会社はFindy Team+ Award 2024, 2025を2年連続で受賞した高い開発生産性を持つ
  • 技術的な側面として「DRYとの向き合い方(境界設計)」が開発生産性に強く作用していた
  • 早期のプロダクト開発では「まずはスピードを最優先」という判断がしばしば下される
  • 短期効率だけを信じて場当たり的な共通化、とりわけ「誤ったDRY」を積み重ねると、ほんの1ヶ月後にはその短期効率こそが最大の足かせになって返ってくる
  • 初速を上げたつもりが境界が溶けたコードベースのメンテに追われ、「本当にやりたい開発」に時間を使えなくなっていく
  • 開発効率は時間とともに自然減衰するものだが、誤った共通化はその減衰曲線を自ら急角度に傾ける
  • Google社も2024年に「Don't DRY your code prematurely」という記事を公開し話題を呼んだ
  • アーキテクチャカンファレンス2025でも「変更容易性(Modifiability)」こそが持続可能な開発の最重要資産であると多くの登壇者が語った
  • AI時代だからこそ「Adaptable AI(AIの変化に適応し続ける設計)」が必要である

■ 2. DRYとOAOOの違い

  • OAOO原則(Once and Only Once):
    • 同じロジックやデータ操作をプログラム中で2回以上定義しないことを目指す考え方
    • 主に「コードの重複」を対象とする
  • DRY原則(Don't Repeat Yourself):
    • 「同じ知識・ビジネスルールが複数箇所にバラバラに埋め込まれている状態」を避け、単一の表現に集約する考え方
    • 主に「知識の重複」を対象とする
  • DRYは単なる「コピペ排除」ではない:
    • 同じコードでも、背負っている「意味」や「前提」が違うなら、必ずしもDRYの対象とは言えない
    • 逆に、コードとしては違っていても、同じビジネスルールを複数箇所に書いているならDRY違反になり得る
  • 本記事で扱う問題は、本来は異なる意味を持つ操作やユースケースを「同じ知識」と見なしてしまい、1つの関数に押し込めてしまった結果の失敗である

■ 3. DRYの誤用が発生する場面

  • 粒度の大きい一連の手続き(ワークフロー)に対して行われる
  • 「同じ処理がありそうだから」という理由だけで共通化する
  • 背景にあるユースケースの違いを無視する
  • 無理に共通化の抽象を導入する
  • 本質的には異なるユースケース・異なる概念を「見た目のコードが似ている」というだけで一つにlumping(ひとまとめ)してしまう問題が起きている
  • これはDRYの本来の目的である「知識を一意に保つ」こととは異なる

■ 4. 具体例:ユーザ作成ユースケースの悪い共通化の段階的悪化

  • ステップ1:素朴な手続き:
    • 「ユーザを1人作成する」というユースケースがフラットに記述されている
    • この時点では問題は少ない
  • ステップ2:別ユースケースでの「ほぼ同じ処理」の登場:
    • 部署の初期構築の一環としてユーザを1人作成するユースケースが追加される
    • 見た目上は重複があるが、この段階ではまだ「ユースケースが2つあるだけ」である
  • ステップ3:「重複しているから」という理由だけの共通化:
    • service_add_userという共通関数にロジックをまとめる
    • まだこの時点では致命的な問題は顕在化していないが、「ユースケースごとにフラットに書かれていたもの」を「大ぶりな共通関数に吸い寄せ始めている」状態である
  • ステップ4:再利用志向がN+1を生む:
    • 「複数ユーザを一括作成する」要件が加わる
    • 既存のservice_add_userを再利用すると、「1人用の処理を、バルク処理でもそのまま流用する」という安易な再利用によってN+1的な非効率I/Oを潜在させてしまう
    • 元々service_add_userは「1人のユーザを作る」ための手続きとして書かれたものであり、「多数ユーザを一括作成する」というユースケースを考慮して設計された抽象ではない
  • ステップ5:オプショナルパラメータ地獄と責務の膨張:
    • 「メールアドレスだけで仮登録できる」ユースケースが追加される
    • service_add_userを拡張して対応すると、name/ageがオプショナルになり、実際の振る舞いは「パラメータの組み合わせ×条件分岐×デフォルト値」に依存し、関数内部に隠蔽される
    • 「通常登録」と「仮登録」という別ユースケースが、どちらも同じ入力型に押し込められる
    • 型定義からは必須なのか任意なのか読み取れない

■ 5. 問題の本質

  • service_add_userは守るべきルールを表す抽象にはなっていない
  • 代わりに複数のユースケースをまとめた「なんでも入り処理関数」になっている
  • DRYの名のもとにやっているのは:
    • ビジネスルールの重複をなくすことではない
    • ユースケースの違いを無視して1箇所に押し込めること(lumping)である
  • 本来DRYを適用すべきなのは守るべきルール(不変条件)そのものであって、手続き・段取り(オーケストレーション)に対してではない

■ 6. 正しい設計:ルールと手順の分離

  • ルールを集約したUserクラスの導入:
    • ユーザに関する「守るべきルール(不変条件)」を集約した構造体
    • このインスタンスが存在する限り、データは常に正しい状態であることを保証する
    • コンストラクタで不正な状態を防ぐ
    • 「通常登録」「仮登録」の違いを生成メソッドで明示する(create_regular, create_reserved)
  • ユースケースは「手順の管理」に徹する:
    • 共通関数を作ろうと頑張るのではなく、ユースケースごとの手順を素直にフラットに書くことに徹する
    • ルールのチェック・状態の正当性はUserクラスに集中する
    • ユースケース層では「どのルートで、何人分、どのような粒度でI/Oするか」を素直に書く
    • 一括作成ユースケースでは必要に応じてbulk_saveなどの最適化を行える
  • 結果として:
    • DRYの対象は「ユーザの整合性ルール」
    • 共通化しない(あえてボイラープレートを許容する)のは「ユースケースごとの手順」

■ 7. 抽象化を導入してよいかを判断するチェックリスト

  • その共通化は「知識」を一意にするか:
    • 単にコードが似ているだけでなく、ドメインルールや整合性チェックを一箇所に集約しているか
  • その関数の責務はユースケースに引きずられていないか:
    • 明らかにユースケースが違うものを同じ関数に押し込んでいないか
    • 新しいユースケースを追加したときにその関数が壊れそうにならないか
  • 「データの整合性を守る構造体」として切り出せるか:
    • その共通処理はデータの正しさを保証するためのものか
    • もしそうなら手続きの一部として書くのではなく、ルールを持った構造体として独立させられないか
  • 共通化によってパフォーマンス制約を固定化していないか:
    • 「1件処理する関数」を共通化した結果、バルク処理の最適化余地を消していないか
  • 変更頻度が高い部分を無理に一箇所に寄せていないか:
    • ビジネスルールとして安定していそうな部分だけを共通化できているか
    • 仕様が変わりやすいユースケース単位の手順は、しばらくは重複を許容したほうが楽ではないか

■ 8. AI支援開発における境界設計の重要性

  • AIのコード生成は「型と境界」を手がかりとして推論する:
    • 関数やメソッドの引数の型、データ構造の形状、インターフェースの粒度、責務の境界などを手がかりにする
    • オプショナルだらけの入力データ定義、ユースケースを無理やりまとめた巨大な関数、前提が暗黙化した共通化はAIが推論するための手掛かりを失わせる
    • 結果としてLLMが誤った前提でコードを生成したり、リファクタリング提案の品質が著しく落ちる
    • これは設計の品質がAI推論可能性に直結していることを示している
  • AIは「明確なルール」を理解しやすい:
    • ルールとデータをひとまとまりの構造体として閉じ込めた設計はAIと非常に相性が良い
    • 構造として明示されているとAIは非常に正確に推論を行える
    • オプション引数の組み合わせ次第で挙動が変わる関数はAIにとって「解釈不能な塊」となる
  • AI支援型リファクタリングは「型の境界」単位で作用する:
    • 型、メソッドの定義、レイヤー境界、依存の方向性が明瞭であるほどAIのリファクタリング精度は劇的に上がる
    • 境界が曖昧だとAIは誤った提案をする
    • 設計の曖昧さはそのまま「AIが壊すリスク」になる
  • 境界が明確だとAIとの協調作業が「加速」する:
    • 既存構造に沿った拡張提案が極めて正確
    • 新ユースケースを自動生成しやすい
    • I/O最適化の提案がしやすい
    • 変更の影響箇所を極めて正確に推論する
    • AIの性能が最大化される

■ 9. AI時代の「速さ」の本質

  • ソースコードを書くコストは劇的に下がった
  • AIを使えば構造の崩れたコードも誤った共通化もこれまでとは比較にならない速度で量産できてしまう
  • これは技術的負債をマッハで積み上げられる時代の到来とも言える
  • 真に競争力を持つのは「最初にコードを書き殴ったチーム」ではない
  • 市場のフィードバックを受け、仕様変更に耐え、システムを破壊することなく安全に進化させ続けられる(Modifiabilityを持つ)チームである
  • 「ルールを守る構造体」や「責務の分離」といった設計はドメイン駆動設計(DDD)における戦術的設計の一部である
  • これらはAIによる爆発的なコード増殖のなかで「変更容易性」という最大の資産を死守するための極めて実利的な防衛手段である
  • ビジネスのコアをコードに反映させ、変更に強い構造を作る「戦略」を持つことがAIという強力な増幅器を積んだ開発者が壁に激突せずにトップスピードで走り続けるための唯一の条件である