
そのインターフェース、使わないメソッドまで実装させてない?🥺
これ絶対あるじゃん??
- クラスに
passとかraise NotImplementedErrorだらけのメソッドがある - インターフェースを実装したら、半分以上使わないメソッドだった
- 1個のメソッドのシグネチャを変えたら、関係ないクラスまでエラーになった
- 「このメソッドなんであるの?」って聞いたら「インターフェースに書いてあるから」って返ってきた
あるよね? あるよね絶対??
これ全部、Fat Interface(ファットインターフェース)、以降「全盛り」って呼ぶね、が原因なんだよね。
てかシンプルに言うとね
使わない仕事まで全員に押し付けてるインターフェースがある。
「それの何がまずいの? pass書いとけばよくない?」ってなる気持ち、わかる!
でもそのpassが本番でサイレントバグになる話、ちゃんとするね。
ちょっと待って、先に全然関係ない話していい?🏠
会社の話するね。
「山田商事(仮名)」っていう会社があって、ここの部長がある日こう宣言したの。
「全社員が全部の仕事をこなせるようにする!」
で、600ページの万能社員マニュアルを作った。中身はこう。
- 営業のやり方(新規開拓から契約書まで)
- 経理の処理(仕訳から決算まで)
- IT管理(サーバー監視からパッチ当てまで)
- 倉庫管理(ピッキングから棚卸しまで)
- カスタマーサポート(電話対応からクレーム処理まで)
全社員がこの1冊のマニュアル研修を受けるの。
新入社員の佐藤さん(経理配属)の初日。600ページのマニュアルを渡された。
わたし経理なのに、フォークリフトの操作とか覚えるの……??
もちろん全部覚えられるわけないから、自分の担当以外は「読んだことにして」サインだけした。
倉庫の田中さん(10年目のベテラン)も同じ。経理の仕訳帳の書き方なんて知らないけど、「研修受けました」のチェックだけ入れた。
で、ある日。消費税率が変わったんだよね。
経理の章だけ書き直しが入って、部長がこうメールした。
「全社員に告知します。マニュアル改訂に伴い、来週月曜に全社研修を行います。出席必須。」
倉庫の田中さんが叫んだ。
「消費税の研修? 俺ピッキングしかやらないんだけど!」
IT部の山本さんも。
「経理の研修? 俺サーバー管理だけなんだけど!」
でも「万能社員マニュアル」に書いてある以上、全員出なきゃいけない。
結果がこれ。
- 全社員が半日研修でつぶれた
- 倉庫の出荷が半日止まって、クライアントへの納品が遅れた → クレーム電話
- ITのサーバーメンテが延期されて、アラートが半日鳴りっぱなしだった
- 佐藤さんは「経理の変更だけなのに、なんで全員巻き込むの……」ってぼやいてた
翌月、ITセキュリティのルールが変わった。また全社研修。また半日つぶれた。
田中さんが退職届を出した。
俺はピッキングがしたいだけなんだよ……
これが本番で起きてるやつが、全盛りインターフェースってこと。
じゃあコードで見てみよっか👀
コードに戻すとこうなるんだよね。万能社員マニュアル = Employeeインターフェース、各社員 = クラスってことで見てみて?
from abc import ABC, abstractmethod
class Employee(ABC):
"""万能社員マニュアル — 全社員これを実装する"""
@abstractmethod
def sell(self, product: str) -> str:
"""営業する"""
...
@abstractmethod
def calculate_tax(self, amount: int) -> int:
"""経理の税計算"""
...
@abstractmethod
def manage_server(self, server_id: str) -> None:
"""ITのサーバー管理"""
...
@abstractmethod
def pick_item(self, item: str) -> None:
"""倉庫のピッキング"""
...
全社員がこの4つ全部を実装しなきゃいけない。倉庫の田中さんはこうなる。
class WarehouseWorker(Employee):
"""倉庫の田中さん"""
def sell(self, product: str) -> str:
return "" # 営業なんてやったことないけど...
def calculate_tax(self, amount: int) -> int:
return 0 # 消費税? 知らんけど0でいいか...
def manage_server(self, server_id: str) -> None:
pass # サーバーってなに
def pick_item(self, item: str) -> None:
print(f"{item}をピッキング") # ← ここだけ本業
calculate_taxがreturn 0を返してるの、見えた?
これがもし本番で呼ばれたらどうなる? 税金0円で計算される。請求書に0円って載る。確定申告がぶっ壊れる。でもエラーは出ない。 0って立派な数字だからね。
「使わないから適当に書いた」コードが本番に流れたとき、誰にも怒られずに0円が帳簿に載るの。わたしも似たようなことやらかしたことあって、テストもグリーンなのに本番でデータがおかしいって言われて調べたら、空実装が原因だったっていう。あれはほんとゾッとした。
やばいのまだあんだけど、バグに気づけない問題😭
万能マニュアルの会社で「全員がちゃんと仕事できてるかチェックして」ってなったとするじゃん。
田中さん(倉庫)のチェック:
- ピッキングが正しいか ← 本業。チェックする
- 営業できてるか ← やってない。何をチェックすんの?
- 税計算が正しいか ← 0って返してるけど、これ正解? 不正解?
- サーバー管理できてるか ←
passしてるだけだけど、OK?
「テストを書く」ってなったとき、passやreturn 0を「正しい動き」としてテストする? それとも「呼ばないからスキップ」する?
スキップした瞬間、テストは「田中さんは税計算OK」ってマークする。でも本番で誰かがcalculate_taxを呼んだら0が返ってくるんだよね。
def test_warehouse_worker():
worker = WarehouseWorker()
worker.pick_item("商品A") # OK
# 以下、テスト書く? 書かない?
assert worker.sell("商品B") == "" # 空文字が「正解」?
assert worker.calculate_tax(10000) == 0 # 0円が「正解」?
# ↑ これ通っちゃうんだよね。テストはグリーン。でもバグ。
全盛りインターフェースは壊れやすいだけじゃなく、バグを作り込む設計でもあるんだよね。
分けてた会社の話もするね〜✨
じゃあ同じ業種で「マニュアルを分けてた別の会社」の話するね。
この会社は、部門ごとにマニュアルを分けてたの。
- 倉庫マニュアル(ピッキング・棚卸し)
- 経理マニュアル(仕訳・決算・税計算)
- IT管理マニュアル(サーバー・セキュリティ)
- 営業マニュアル(新規開拓・契約)
倉庫の人は倉庫マニュアルだけ、経理の人は経理マニュアルだけ読めばいい。
同じように消費税率が変わったとき、どうなったか。
- 経理マニュアルだけ改訂した
- 研修は経理の3人だけ
- 倉庫は通常稼働 → 納品遅延なし
- ITもメンテ続行 → アラートなし
- 田中さんは「消費税? 知らんけどピッキングあるんで」って通常運転
何が起きなかったかっていうと:
- 全社員が半日つぶれること → 起きてない
- 納品遅延のクレーム → 起きてない
- アラート鳴りっぱなし → 起きてない
- 田中さんの退職届 → 起きてない
コードに戻すとこういうことなんだよね。
from abc import ABC, abstractmethod
class Seller(ABC):
"""営業マニュアル"""
@abstractmethod
def sell(self, product: str) -> str: ...
class TaxCalculator(ABC):
"""経理マニュアル"""
@abstractmethod
def calculate_tax(self, amount: int) -> int: ...
class ServerAdmin(ABC):
"""IT管理マニュアル"""
@abstractmethod
def manage_server(self, server_id: str) -> None: ...
class ItemPicker(ABC):
"""倉庫マニュアル"""
@abstractmethod
def pick_item(self, item: str) -> None: ...
class WarehouseWorker(ItemPicker):
"""倉庫の田中さん — ピッキングだけ"""
def pick_item(self, item: str) -> None:
print(f"{item}をピッキング")
class Accountant(TaxCalculator):
"""経理の佐藤さん — 税計算だけ"""
def calculate_tax(self, amount: int) -> int:
return int(amount * 0.1)
WarehouseWorkerはItemPickerだけを実装してる。calculate_taxなんてメソッドはそもそも存在しない。
消費税率が変わったとき → TaxCalculatorを実装してるクラスだけ影響。WarehouseWorkerは1行も触らない。テストも再実行不要。
「知らないメソッドの空実装」が消えたから、サイレントバグも消えたってこと。
てかこれ、第1回のSRPと似てるって思わなかった? SRPは「クラスの仕事を1つにしろ」って話だった。ISPは「インターフェースの仕事も必要な分だけにしろ」って話。根っこは同じで、「関係ないものを巻き込むな」っつーこと。
あと、第11回のLSPで見た「約束破り」——あのraise NotImplementedErrorが生まれる原因の1つが、この全盛りインターフェースなんだよね。使わないメソッドを無理やり実装させるから、passやNotImplementedErrorが生まれちゃう。インターフェースを分ければ、そもそも実装する必要がなくなるから約束破りも起きないってわけ。
ちゃんとした名前もあるから一応言うね📚
インターフェース分離の原則(Interface Segregation Principle、以降ISP)とは、「使わないメソッドまで付いてくるインターフェースを押し付けるな」っつーこと。
アンクルボブ(Robert C. Martin)がXeroxのプリンターのソフトウェアを直してたとき、1つのデカいインターフェースのせいで書き直しが全クラスに広がってたのを見て「これダメだわ」って言い出したやつ。SOLIDの「I」がこれね。
難しそうに聞こえるけど、要は**「この人に関係ない仕事まで押し付けてない?」**を問い続けるだけなんだよね。
これだけ覚えて帰って🫶
合言葉はこれ。psan(passありえん)
コードを書くとき、この問いを投げてみて。
「このインターフェースのメソッド、実装してるクラス全員がほんとに全部使ってる?」
もし「使ってないけどpassで埋めてる」クラスがいたら、それは全盛り。
たとえば:
WarehouseWorkerがcalculate_taxをreturn 0で実装してる ← このインターフェース、分けるべきSimplePrinterがfaxをpassで実装してる ← プリンターとFAXは別の仕事
passが1個でもあったら「このメソッド、ここにあるべき?」って疑う。 それだけで全盛りインターフェースは防げるっつーこと。
まとめるとこういうことね
| 観点 | 全盛りインターフェース(ISP違反) | 分けたインターフェース(ISP準拠) |
|---|---|---|
| メソッドの数 | 使わないメソッドも含めて全部実装する | 必要なメソッドだけ実装する |
| 空実装 | passやreturn 0が散在する | そもそも不要なメソッドがない |
| 変更の波及 | 1つのメソッド変更が全クラスに影響 | 関係あるクラスだけ影響する |
| テスト | 空実装を「正しい」としてテストする矛盾 | 実装してるメソッドだけテストすればいい |
| SRPとの関係 | インターフェースが複数の仕事を抱えてる | インターフェースも1つの仕事だけ |
| LSPとの関係 | 空実装がLSP違反(約束破り)の原因になりやすい | 実装すべきものだけ実装するからLSP違反が起きにくい |
「この人に関係ない仕事まで押し付けてない?」——それを問い続けるだけで、passだらけのクラスは消えるっつーこと。
沼りたい人はこっちも読んで📖
権威ある書籍
- Robert C. Martin『Agile Software Development: Principles, Patterns, and Practices』(2002) — ISPの原典。Xeroxのプリンター問題から生まれた背景が書いてある
- Robert C. Martin『Clean Architecture』(2017) — ISPを含むSOLID原則を設計全体で解説
- Martin Fowler『Refactoring: Improving the Design of Existing Code』(2018) — 太すぎるインターフェースのリファクタリング手法が載ってる
一次資料
- Wikipedia — Interface segregation principle: https://en.wikipedia.org/wiki/Interface_segregation_principle
- Wikipedia — SOLID: https://en.wikipedia.org/wiki/SOLID
無料で読める入門記事
- DigitalOcean「SOLID Design Principles Explained」: https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
- Zenn・Qiitaで「インターフェース分離の原則」で検索すると日本語の実装例が見つかる
Dev-Here「ギャルでも分かる設計の勘ドコロ!」シリーズ 第12回