発展LESSON 17

この教材で学ぶこと 責務と依存を整理し、testで守りながら構造を改善します。

設計とリファクタリング

対象読者: 動くコードを、変更しやすく安全な構造へ育てたい人。

この教材でできるようになること

  • 責務、凝集度、結合度を変更理由から説明できる
  • domain logicとI/Oを分離できる
  • testで振る舞いを守りながら小さくrefactorできる
  • abstractionを導入する時期と、導入しない判断ができる

1. 良い設計は変更を局所化する

設計の目的はclass数を増やすことではありません。要件変更が来たとき、どこを直せばよいか分かり、無関係な場所を壊しにくい構造を作ることです。

次を観察します。

  • 一つの関数が複数の理由で変わる
  • 同じ判断が複数箇所に重複する
  • 外部serviceがないとcore ruleをtestできない
  • 名前から副作用や失敗条件が分からない
  • 小さな変更で多数のmoduleが連鎖的に変わる

2. domain logicとI/Oを分ける

def calculate_total(prices: list[int], tax_rate: float) -> int:
    if any(price < 0 for price in prices):
        raise ValueError("price must be non-negative")
    if not 0 <= tax_rate <= 1:
        raise ValueError("tax_rate must be between 0 and 1")
    return round(sum(prices) * (1 + tax_rate))

この関数はHTTP、DB、画面を知りません。adapterが外部入力を変換し、domain functionへ渡します。

HTTP / CLI / UI
      ↓ adapter
 application use case
      ↓
  domain logic
      ↑ interface
 DB / external API

3. 名前で境界を明らかにする

process()、handle()、dataだけでは意図が分かりません。

  • validate_registration_request
  • calculate_discounted_total
  • save_learning_progress
  • fetch_lesson_catalog

名前が長くなりすぎる場合、関数の責務が広すぎる可能性があります。

4. 小さくrefactorする

安全な順序:

  1. 現在の振る舞いをtestで固定する
  2. 名前変更や関数抽出を一種類だけ行う
  3. testを実行する
  4. 差分を読む
  5. 次の小さな変更へ進む

refactoring中に新機能を混ぜると、失敗原因が構造変更か仕様変更か分かりにくくなります。

5. abstractionを急がない

似て見える二つの処理でも、将来別々に変わるなら統合しないほうがよい場合があります。

抽象化する前に確認します。

  • 本当に同じbusiness ruleか
  • 重複は何回あり、どのように変化したか
  • 呼び出し側は同じ契約を必要としているか
  • 新しい抽象化の名前をdomain用語で説明できるか
  • 削除や差し替えが容易か

6. dependency direction

core ruleがframeworkの型やDB clientへ直接依存すると、technology変更がdomain全体へ広がります。外側が内側へ依存する形を目指します。

ただし小さなscriptで何層ものinterfaceを作る必要はありません。変更頻度、寿命、team規模、testの必要性に合わせます。

7. review checklist

  • 入力境界でvalidationしているか
  • function名と戻り値から責務が分かるか
  • errorを握りつぶしていないか
  • logへ秘密情報を出していないか
  • 時刻、乱数、networkをtestで差し替えられるか
  • 変更に対応するtestがあるか
  • rollback可能な単位か

演習: 一つの巨大関数を分ける

「JSONを読む → 入力検証 → 合計計算 → DB保存 → メール送信」を一関数で行うcodeを想定し、次へ分けてください。

  1. domain valueとvalidation
  2. 合計を求めるpure function
  3. application use case
  4. storage interface
  5. notification interface
  6. CLIまたはHTTP adapter

各境界で起きるerrorと、どこで利用者向けmessageへ変換するかも決めます。

理解チェック

  • 変更理由から責務を分けられる
  • core logicを外部I/Oなしでtestできる
  • 振る舞いを維持した小さなrefactorができる
  • 不要なabstractionを作らない理由を説明できる

次に進む

ここまでの教材から一つの小さな作品を完成させます。CLI、静的Webページ、APIのどれかを選び、README、入力検証、test、実行手順、制約を揃えて公開できる形へ仕上げてください。