メインコンテンツまでスキップ

第05章:境界(Boundary)の考え方 🚧😊

“内側(ルール)”と“外側(I/O)”を分けると、コードが一気に強くなるよ💪✨ この章では「境界ってなに?どう作るの?」を、ふわっと→しっかりの順でつかみます🧠🌸


5-1. まず「境界」ってなに?🤔🚧

境界(Boundary)は、ざっくり言うと……

  • 内側:アプリの“ルール”や“判断” 🧠📦

    • 例:税計算、割引判定、入力値チェック、期限判定、在庫の引当、料金計算…など
  • 外側:アプリの“外の世界(I/O)” 🌍🔌

    • 例:DB🗄️、ファイル🗂️、ネット🌐、時刻🕰️、乱数🎲、UI🖥️、メール📧 など

そして 境界は「内側と外側の接点」 です📦↔️🌍 この接点をちゃんと作ると、テストがめちゃ楽になります🧪✨


5-2. なんで境界を作ると嬉しいの?🎉

境界を作ると、こうなります👇

  • 内側(ルール)が純粋に近づく 🌿✨ → テストが「速い・安定・簡単」⚡🧪
  • 外側(I/O)を交換できる 🔁 → DBを本物→Fake、時間を本物→固定、APIを本物→Fake に差し替え可能🎭
  • 変更に強い 🛡️ → “外の都合”が変わっても、内側は守られる🏰✨

境界がないと「I/Oが混ざってテストが揺れる」😵‍💫 境界があると「I/Oを差し替えてテストが固定できる」🎯✨


5-3. “境界がある世界”のイメージ図 🗺️✨

ポイントはこれ👇 「内側が外側に依存しない」(超だいじ!)🙌

  • 内側:必要なものを「こういう機能がほしい」と宣言する(=インターフェース)🧩
  • 外側:それを実装する(=本物I/O)🔌
  • 組み立て:最後に接続する(後の章の Composition Root 🏗️)

イメージ:

  • 内側(ルール)📦

    • IClock(今の時刻が欲しい)🕰️
    • IUserRepository(ユーザー取得したい)🗄️
  • 外側(I/O)🌍

    • SystemClock(DateTimeを読む)
    • SqlUserRepository(DBに行く)

5-4. 境界は「インターフェース」で作るのが定番 🧩✨

testable_cs_study_005_boundary_socket.png

ここでの主役が インターフェース です🧩💖 内側は「欲しい能力」を インターフェースとして定義 します✍️

例:時刻は I/O なので境界にする🕰️🚧

public interface IClock
{
DateTime Now { get; }
}

外側が本物実装:

public sealed class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}

内側は DateTime.Now を直接読まないで、IClock を使うようにするんだよ〜🙆‍♀️✨


5-5. “依存の向き”が超重要!🔁➡️

ここ、最初はピンと来なくてOKなんだけど…すごく大事なのでやさしく言うね😊🌸

  • ❌ ダメになりがち:内側(ルール)が外側(DB/HTTP/ファイル)を直呼び → ルールが外の都合に縛られて、テストがつらい😵‍💫
  • ✅ 目指す:内側(ルール)が「必要なもの」を インターフェースで要求 → 外側が後から実装して合わせに来る🚶‍♀️🔌

つまり…

内側は“どうやるか”を知らない 内側は“何が欲しいか”だけ言う 🧠✨

この状態ができると、テストで差し替えが効きます🎭🧪


5-6. ミニ例:境界があるとテストがラクになる🎉

内側(ルール)に「期限チェック」があるとします⏳ DateTime.Now を直読みするとテストが揺れます🌀

境界を使うとこう👇

public sealed class SubscriptionService
{
private readonly IClock _clock;

public SubscriptionService(IClock clock)
{
_clock = clock;
}

public bool IsExpired(DateTime expiresAt)
{
return _clock.Now > expiresAt;
}
}

テストでは FakeClock を使って「今」を固定できる🎯✨

public sealed class FakeClock : IClock
{
public DateTime Now { get; set; }
}

こうすると「今日がいつか」に関係なくテストが安定します🧪💖


5-7. 境界の作り方のコツ 🧠🧩

コツ①:境界は “欲しい能力” で切る ✂️✨

  • IClock(時刻)🕰️
  • IEmailSender(メール送信)📧
  • IFileStore(ファイル保存)🗂️
  • IExternalApiClient(外部API)🌐

「何をしたいか」で分けるのが気持ちいいです😊✨

コツ②:インターフェースは小さめが正義 🧩💎

でかい万能インターフェースは破綻しがち😵‍💫 最初は 1〜3メソッドくらいを目安に👍✨

コツ③:内側で “実装の都合” を漏らさない 🚫🫧

例えば DB を使うからって、内側に SqlConnection とか出すとアウト🙅‍♀️ 内側に必要なのは 「ユーザーを取得したい」 みたいな要求だけでOK🙆‍♀️✨


5-8. よくある落とし穴(あるある)😇💥

  • 境界を作ったのに、結局内側が外側の型に依存してる 😵

    • 例:内側が HttpResponseMessage を返す、DbContext を握る…など
  • 境界を作りすぎて抽象が増えすぎる 🌪️

    • なんでもかんでも IWhatever にして迷子😵‍💫
    • 対策:まずは「テストが困るI/O」からでOK🧪✨

5-9. 章末ミニまとめ 🧾✨

  • 境界とは 内側(ルール)📦外側(I/O)🌍 の接点🚧
  • 境界を作ると 差し替えできてテストが安定 🎭🧪
  • 境界はだいたい インターフェースで作るのが定番🧩
  • 内側は「何が欲しいか」を言うだけ、外側が実装する🔁✨

※ちなみに本日時点では、.NET 10(LTS)は 2025/11/11 リリースでサポートは 2028/11/14 まで、C# は C# 14 が最新(.NET 10 対応)です🆕✨ (Microsoft)


5-10. 演習(ゆるめ)✍️😊

次のコードの「外側(I/O)」を探して、境界にできそうなものを丸してみてね✅✨

  • DateTime.Now 🕰️
  • Random.Shared 🎲
  • File.ReadAllText 🗂️
  • HttpClient.GetStringAsync 🌐
  • Console.ReadLine / WriteLine 🖥️
  • DBアクセス系(EF/SQL)🗄️

「丸したもの=境界候補」だよ〜🚧💖


5-11. AI(Copilot/Codex)に手伝わせるプロンプト例 🤖💡

コピペでOK系👇✨

  • 「このコードの I/O(外部依存)を列挙して、境界(インターフェース)案を出して」🔍🧩
  • 「DateTime.Now を直接使ってる箇所を IClock に置き換えて」🕰️➡️🧩
  • 「テストが安定するように Fake 実装も作って」🎭🧪
  • 「この設計、内側が外側に依存してないかチェックして」👀✅

AIの提案は便利だけど、**“内側に外側の型が漏れてない?”**だけは毎回チェックね⚠️💖


次の第6章では、ここで作った境界の考え方を 最小サンプルで手を動かして体験していくよ🧪✨ 「分けると一瞬でテストできる」感覚、味わいにいこ〜!🎉🚀