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

第19章:ISPの入口「太いインターフェースは使う人を苦しめる」😵‍💫✂️

この章はひとことで言うと… **「使わないメソッドまで“知ってることにされる”のをやめよう」**って話だよ〜!😊✨


1) ISPってなに?(超やさしく)📌😊

Universal Remote (complex) vs Specific Purpose Remote (simple).

ISP(Interface Segregation Principle)は、だいたいこういう意味👇

  • 使わないメソッドに依存させない
  • “万能インターフェース”をやめて、用途別に小さくする

有名な言い方だと👇 **「クライアントは、使わないメソッドに依存させられるべきではない」**って感じ🫶✨ (Object Mentor)

イメージ: 「テレビ見るだけなのに、エアコン・照明・カーテンまでボタンが付いてるリモコン」📺🧊💡😇 使う側も、作る側も、事故りやすいよね〜!


2) “太いインターフェース”が生む地獄あるある🔥😇

あるある①:実装クラスがムダに苦しい💦

  • 使わないメソッドまで 実装を強制される
  • 結果:throw new NotImplementedException() が出てくる💣

あるある②:利用側が事故りやすい⚠️

  • 画面は「参照だけ」したいのに Delete() が見えてる😨
  • “間違って呼べる” = “間違いが起きる” 🫠

あるある③:変更の波及が増える🌊

  • 1個の変更(例:保存の仕様)で、参照しかしてない画面まで影響…
  • 依存が太いほど、影響範囲が太る🫃(太るな!😂)

3) ISP違反を見抜くチェックリスト✅🕵️‍♀️

次のどれかが出たら、ISP疑い濃厚だよ〜!✨

  • IService / IManager / IRepositoryなんでも屋になってる🧺
  • メソッド数が増え続けて 10個超えが当たり前😵
  • 実装に ダミーNotImplemented が混ざる💣
  • テスト用Fakeが作れなくて 泣く😭
  • 「参照だけの人」まで「更新系メソッド」を知ってる📖✍️

4) 例:ミニECの「太すぎRepository」問題🛒💥

まず、わざと“太い”やつを見てみよっか😈

public interface IOrderRepository
{
// 読み取り
Order? GetById(Guid id);
IReadOnlyList<Order> Search(string keyword);

// 更新
void Add(Order order);
void Update(Order order);
void Delete(Guid id);

// 永続化・トランザクションっぽいもの
Task SaveChangesAsync(CancellationToken ct = default);
IDisposable BeginTransaction();
}

参照画面(検索)だけしたいクラスなのに…😇

public sealed class OrderQueryService
{
private readonly IOrderRepository _repo;

public OrderQueryService(IOrderRepository repo) => _repo = repo;

public IReadOnlyList<Order> Search(string keyword)
=> _repo.Search(keyword);
}

この OrderQueryServiceSearchしか使ってないのに、依存は IOrderRepository 全部🥲 つまり Delete/SaveChanges/BeginTransaction まで“知ってる側” になっちゃうの。

テストでも地獄(Fakeが太る)😭🧪

public sealed class FakeOrderRepository : IOrderRepository
{
private readonly List<Order> _orders = new();

public Order? GetById(Guid id) => _orders.FirstOrDefault(x => x.Id == id);
public IReadOnlyList<Order> Search(string keyword) => _orders;

public void Add(Order order) => throw new NotImplementedException();
public void Update(Order order) => throw new NotImplementedException();
public void Delete(Guid id) => throw new NotImplementedException();
public Task SaveChangesAsync(CancellationToken ct = default) => throw new NotImplementedException();
public IDisposable BeginTransaction() => throw new NotImplementedException();
}

参照テストしたいだけなのに、未使用メソッドのせいで、Fakeが爆発する〜〜〜😭💥


5) ISPの解決: “使う人ごと”にインターフェースを分ける✂️✨

ポイントは超シンプル👇 「誰(クライアント)が、どれを使うの?」を先に分ける😊

Step 1:クライアント(利用者タイプ)を出す👥

例)

  • 参照画面(検索・詳細)📖
  • 管理画面(追加・更新・削除)✍️
  • バッチ(出荷ステータス更新)🤖

Step 2:使うメソッドだけをグルーピング🧩

  • 参照だけ → “読むインターフェース”
  • 更新だけ → “書くインターフェース”

Step 3:小さいインターフェースに分割✂️

public interface IOrderReader
{
Order? GetById(Guid id);
IReadOnlyList<Order> Search(string keyword);
}

public interface IOrderWriter
{
void Add(Order order);
void Update(Order order);
void Delete(Guid id);
Task SaveChangesAsync(CancellationToken ct = default);
}

✅ これで「参照画面」は IOrderReader だけ知ってればOK! ✅ 「管理画面」は IOrderWriter を使えばOK!

参照サービスはこうなるよ👇✨

public sealed class OrderQueryService
{
private readonly IOrderReader _reader;

public OrderQueryService(IOrderReader reader) => _reader = reader;

public IReadOnlyList<Order> Search(string keyword)
=> _reader.Search(keyword);
}

Fakeも一気に軽くなる〜!🥹💕

public sealed class FakeOrderReader : IOrderReader
{
private readonly List<Order> _orders = new();

public Order? GetById(Guid id) => _orders.FirstOrDefault(x => x.Id == id);
public IReadOnlyList<Order> Search(string keyword) => _orders;
}

6) 分割するときの命名のコツ(初心者向け)📝✨

迷ったらこのどれかでだいたい勝てるよ👍

  • IOrderReader / IOrderWriter(読み書きで分ける)📖✍️
  • IOrderQueryService / IOrderCommandService(用途で分ける)🔎🛠️
  • IOrderSearch / IOrderDetail(画面単位で分ける)🖥️

大事なのは “実装都合”じゃなく“利用者都合” で切ることだよ😊


7) Visual Studioでの実践ヒント🪄🪟

  • **Find All References(参照の検索)**で「誰が何を使ってるか」を洗い出す🔎
  • クラスから Extract Interface して、必要なものだけ残す✂️
  • いきなり全部分けず、まずは 「参照用」「更新用」 の2つに割るのが成功しやすい😊✨

8) “分けすぎ”注意!⚖️😅(ISPの落とし穴)

ISPは大事なんだけど、こうなると逆に辛いよ〜👇

  • IOrderGetById とか 1メソッド1インターフェース 量産😵‍💫
  • プロジェクト全体がインターフェースだらけで迷子🧭

目安としては、

  • 「利用者タイプが違う」
  • 「変更理由が違う」
  • 「テストで差し替えたい境界」

このへんが揃ったら分けどき!✨


9) 🤖AI(Copilot/Codex系)に頼むと超はかどるプロンプト集💖

① “太い原因”を見つける

  • 「このインターフェースが太い理由を、利用者視点で箇条書きして。未使用メソッド依存のリスクも」

② “誰が何を使うか”をマトリクス化

  • 「参照画面/管理画面/バッチの3種類の利用者を想定して、必要メソッド一覧表を作って」

③ 分割案を作る

  • 「ISPに従って、役割別インターフェース(Reader/Writerなど)へ分割して。命名案を3パターン出して」

④ 安全に直す手順(コミット単位)

  • 「破壊しないリファクタ手順をコミット単位で提案して(1コミット=1目的)」

さらに、リポジトリに カスタム指示を置くと、設計の癖が統一されて強いよ🧠✨ GitHub Copilot は “リポジトリのカスタム指示ファイル” に対応してるよ(Visual Studio も選べる)📄🤝 (GitHub Docs)


10) まとめ🎀✨

  • ISPは 「使わないメソッドに依存させない」 原則だよ😊 (Object Mentor)
  • “太いインターフェース”は 実装もテストも利用もつらくする😭
  • 解決は 利用者ごと(役割ごと)に小さく分割✂️✨ → Fakeが軽くなる、事故が減る、変更が局所化する💪

次章予告(第20章)📖✍️✨

次はまさに今日の続き! 「読み取り用/更新用」を実戦で分けて、設計をキレイにするよ〜!🥳🎉


(最新環境メモ)🧷

この教材の前提になってる .NET 10 は 2025/11/11 リリースのLTSで、サポートも継続中だよ✅ (Microsoft) Visual Studio 2026 も提供されていて、C# 14 の新機能も “VS 2026 や .NET 10 SDK” で試せるよ✨ (learn.microsoft.com)