第14章:抽象化の「さじ加減」🍰⚖️(やりすぎ防止のOCP)
この章でできるようになること 🎯✨
- 「抽象化した方がいい場面/しない方がいい場面」を見分けられる 👀✅
- “差し替え口”(拡張ポイント)を、必要な場所にだけ作れる 🚪🔁
- 未来の変更に強いけど、今の自分が読めるコードにできる 🧸📖

まず結論:抽象化は「未来の変更のための穴」🚪🧩
OCPって「追加に強くする」イメージだけど、実際は…
- ✅ 変わりそうなところに“差し替え口”を作る
- ❌ 変わるか分からないところまで先回りして抽象化しない
このバランスがすべてだよ〜⚖️😊
ちなみに今の最新のTypeScriptは 5.9系(公式の5.9リリースノートが2026/01/07更新)だよ📌✨ (TypeScript) (なので、下のコード例も「いま普通に使われる書き方」をベースにしてるよ〜)
やりすぎ抽象化あるある 🥲💥(見つけたら黄色信号)
次の症状が出てたら「ちょい盛りすぎ」かも🚧⚠️
- 🌀 抽象が増えてるのに、追加は楽になってない
- 🧩 interface / abstract / generics が増えて、読むのがしんどい
- 🧙♂️ 「将来のために…」って言いながら、その将来が具体的じゃない
- 🧱 1個しか実装が無いのに、戦略・工場・基底クラスがいる
- 🧪 テストが無いのに抽象化してて、怖くて触れない
抽象化は “賢さ” じゃなくて、変更コストを下げる道具だよ🛠️✨
抽象化は「3段階」で育てるのが安全 🌱➡️🌳

いきなり大きい仕組みにしないで、段階的にレベルアップすると失敗しにくいよ😊✨
🥚Lv0:まずは素直に書く(if/switch)
「まず動く」「仕様が固まってない」ならこれでOK👌
type CouponCode = "STUDENT" | "RAIN";
function calcDiscount(code: CouponCode, totalYen: number): number {
switch (code) {
case "STUDENT":
return Math.floor(totalYen * 0.1);
case "RAIN":
return 200;
}
}
いいところ:読みやすい📖 弱点:種類が増えると分岐が育って地獄になりがち🌋
🐣Lv1:「データ駆動」にする(関数マップ)
分岐が増えそうだけど、まだ “仕組み” を作りたくないときに最強💪✨
ここで便利なのが satisfies 👑
「型チェックはするけど、推論のうまみは残す」って感じ🍯
(satisfies は TypeScript 4.9 からの機能だよ) (TypeScript)
type CouponCode = "STUDENT" | "RAIN";
type DiscountFn = (totalYen: number) => number;
const discountRules = {
STUDENT: (totalYen: number) => Math.floor(totalYen * 0.1),
RAIN: (_totalYen: number) => 200,
} satisfies Record<CouponCode, DiscountFn>;
export function calcDiscount(code: CouponCode, totalYen: number): number {
return discountRules[code](totalYen);
}
いいところ:
- 追加は1行でOK(分岐が増えない)🧩✨
CouponCodeを増やしたのにルールを書き忘れたら、型が怒ってくれる😤✅
まだやらない方がいいこと:
- この段階で interface + class にしない(必要になってからでOK)🙅♀️
🐥Lv2:本気の“差し替え口”(Strategy / DI)
次の条件がそろってきたら、戦略パターンが輝く🌟
- 割引の種類が増えるだけじゃなくて、割引ごとに必要データが違う
- 管理画面や外部設定で 動的に差し替えたい
- テストで実装を差し替えたい(本番/テスト)🎭
type Money = number;
export interface DiscountPolicy {
code: string;
calc(total: Money): Money;
}
export class StudentDiscount implements DiscountPolicy {
code = "STUDENT";
calc(total: Money): Money {
return Math.floor(total * 0.1);
}
}
export class RainDiscount implements DiscountPolicy {
code = "RAIN";
calc(_total: Money): Money {
return 200;
}
}
export class DiscountService {
private map: Map<string, DiscountPolicy>;
constructor(policies: DiscountPolicy[]) {
this.map = new Map(policies.map(p => [p.code, p]));
}
calc(code: string, total: Money): Money {
const policy = this.map.get(code);
if (!policy) return 0;
return policy.calc(total);
}
}
いいところ:
- 追加はクラス1個増やして配列に入れるだけ🎉
- テストで「偽の割引」を差し込める🧪✨
注意点:
- 「2種類しかない」「当面固定」なら Lv1 で十分なことも多いよ😊
じゃあ、いつ抽象化するの?👀🧠(判断チェックリスト)
迷ったら、この質問に答えてみて📝✨
- 🔁 同じ形の変更が、今後2回以上来そう?
- 🧨 追加のたびに 同じ場所を毎回修正してる?(分岐の中心とか)
- 🧱 追加するときに、既存のテストが壊れやすい?
- 🧩 追加対象が「割引」「支払い」「通知」みたいに、明確な“種類”で増える?
- 🔌 追加が「差し替え」っぽい?(A/B、環境、本番/テスト)
- 🧑🤝🧑 チーム(未来の自分)が読める?(読めないなら負債)
- ✅ テストで守れる?(守れない抽象化は怖いだけ)
3つ以上 YES なら、抽象化を検討してOK👌✨ 1つもYESが無いなら、今はやらないのが正解率高め😊
抽象化の「粒度」:大きく作らないコツ 🍡✨
抽象化って、でかくすると失敗しやすいの…🥲
✅ 良い抽象の特徴
- 役割が1行で言える(例:「割引を計算する」)🗣️
- メソッドが少ない(1〜3個くらい)🧼
- 名前が「実装」じゃなくて「意図」になってる(
DiscountPolicyなど)🎯
❌ 事故る抽象の特徴
IDiscountStrategy<TContext, TResult, TEnv>みたいにジェネリクス盛り盛り🍔💥BaseService/AbstractManagerみたいな “何でも屋” 👷♂️- 使われてないメソッドが多い(ISP的にもNG寄り)🧻
ミニ演習(Campus Café)☕️📦✨
演習1:どれを選ぶ?当てはめクイズ 🎯
次の未来、あなたなら Lv0/Lv1/Lv2 どれ?🤔
- A) クーポンは2種類固定。増える予定なし
- B) 月1で増える。増えるたびに分岐が長くなる
- C) 店舗ごとに割引が違う。設定で切り替えたい
答え(目安)👇
- A) Lv0 ✅
- B) Lv1 ✅
- C) Lv2 ✅
演習2:Lv0 → Lv1 リファクタしてみよ🧼✨
やることはこれだけ👇
switchをやめる- 関数マップにする
satisfiesで「書き忘れ」を防ぐ
(上の Lv1 コードを写経でOKだよ📝💗)
演習3:Lv1 → Lv2 が必要になる条件を“自分の言葉”で書く📝✨
たとえばこんな感じ👇
- 「店舗ごとに割引が違うから、起動時にポリシー差し替えたい」
- 「テストで割引の実装を入れ替えたい」
- 「割引ごとに必要な外部データが増えてきた」
AI活用コーナー 🤖💡(抽象化を“盛らせない”指示がコツ)
AIに相談するときは、最初に“軽い案”を出させるのが安全だよ😊
使えるプロンプト例 🪄
- 「まずLv0(素直な実装)で書いて、その後にLv1案(関数マップ)も出して。最後にLv2案(Strategy)も出して。メリット/デメリットを比較して」
- 「将来の変更が“月1で増える”想定。最小の抽象化でOCPに寄せたい」
- 「今あるテストを壊さない手順で、小さくリファクタのステップを書いて」
AIの出力を採用する前のチェック✅
- 🧠 “将来”が具体的か?(妄想抽象化になってない?)
- 🧪 テストが増えてるか?(増えてないなら危ない)
- 📖 自分が3日後に読んで理解できるか?
まとめ 🌸✨
- 抽象化は「差し替え口」だけど、作りすぎると逆に弱くなる⚖️
- Lv0 → Lv1 → Lv2 の順で育てると安全🌱
satisfiesを使うと「追加時の書き忘れ」を型で防げて気持ちいい💎 (TypeScript)- 迷ったらチェックリスト。YESが少ないなら、今は素直に行こう😊👍
ミニ課題(提出用)🎁📝
- クーポンを1つ追加(例:
SET= 300円引き) - Lv1方式で追加して、既存コードの修正が最小になってるか確認✨
- 「このケースはLv2が必要?」を2〜3行で説明💬😊
やってみよ〜〜っ!🎉☕️✨