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

第6章:条件分岐のDRY(同じifが増える地獄を止める)🌪️➡️🌿

0) この章のゴール🎯✨

「同じ条件チェック(=同じルール)」がコードのあちこちに散らばって、あとで変更が地獄になるのを止めます😇 この章が終わると、こんな力がつくよ💪💖

  • ✅ 「条件(if)=知識(ルール)」だって気づける
  • ✅ 条件を 1か所 に集める(ガード関数・判定関数)
  • ✅ TypeScriptの ナローイング(型が賢くなるやつ)も一緒に使える🧠✨
  • ✅ 条件が増えても崩れにくい形(Union+網羅チェック)にできる🙆‍♀️

※ちなみに現時点のTypeScript最新版は npm 上で 5.9.3 として配布されています。(npmjs.com)


1) まず知ってほしい:ifは「分岐」だけじゃない🧠🪄

if文って、見た目はただの分岐だけど… 本質は 「ルール(知識)の判定」 なんだよね👀✨

たとえば👇

  • 「会員は10%引き」
  • 「合計5000円以上なら送料無料」
  • 「管理者だけこの操作OK」
  • 「ステータスがPAIDのときだけ発送できる」

これ全部、仕様=知識 だよね📌 だから同じifが増えるってことは…

✅ 同じ知識が複数箇所にコピーされてる(=DRY違反) ってことになりやすいの🥹💦


2) よくある“同じif増殖”パターン3つ😱📛

パターンA:同じ条件が散らばる(変更で死ぬ)💀

「送料無料条件」が 3か所にある、とかね🫠

パターンB:条件式が長くて毎回コピペ(読みづらい)📄

user && user.role === "admin" && user.plan !== "free" && ... みたいなやつ😵‍💫

パターンC:条件の意味が毎回ちょっと違う(バグ温床)🐛

「A画面では >= 5000 だけど、B画面では > 5000」みたいな微妙差…😇


3) テク①:まずは「条件に名前をつける」📛✨(最速で効く!)

同じ条件が出てきたら、まずこれだけでも世界が変わるよ🌍💖

❌ WET(意味が読めない&コピペしがち)

if (subtotal >= 5000 && user.isMember) {
// ...
}

if (subtotal >= 5000 && user.isMember) {
// ...
}

✅ DRY(条件に“名前”がつくと読みやすい!)

const isFreeShipping = subtotal >= 5000 && user.isMember;

if (isFreeShipping) {
// ...
}

if (isFreeShipping) {
// ...
}

ポイントはこれ👇

  • 条件の意味が一瞬で分かる👀✨
  • 変更が入っても 1か所直せばOK🔧
  • 「何を満たすとtrueなの?」が説明しやすい📝

4) テク②:ガード関数(isXxx)で条件を“1か所化”🛡️✨

ここからが第6章のメインディッシュ🍽️💖 条件チェックを関数にして、散らばりを根絶しよう!

4-1) まずは普通の判定関数でOK🙆‍♀️

function isFreeShippingEligible(subtotal: number, isMember: boolean) {
return isMember && subtotal >= 5000;
}

// 使う側
if (isFreeShippingEligible(subtotal, user.isMember)) {
// 送料無料処理
}

「送料無料ルール」が この関数に集約されるのが最高👍✨


4-2) TypeScriptらしく:型ガード(type predicate)で“型も絞る”🧠✨

TypeScriptは、条件によって型を絞り込む(ナローイング)機能があるよね🧩 その中でも 自分で作れる最強フォームが「型ガード関数」💪

Type predicate(x is Type)の形で書くと、ifの中で型が確定するよ✨(TypeScript)

例:支払い方法がユニオンのとき👇

type Payment =
| { kind: "card"; last4: string }
| { kind: "bank"; accountId: string }
| { kind: "cash" };

function isCardPayment(p: Payment): p is { kind: "card"; last4: string } {
return p.kind === "card";
}

function showPaymentLabel(p: Payment) {
if (isCardPayment(p)) {
// ここでは p.kind は "card" に確定してる✨
return `CARD **** ${p.last4}`;
}
return p.kind.toUpperCase();
}

これのうれしさ👇😍

  • ifの中で 補完が強くなる
  • 「カードのときだけ使えるプロパティ」が安全に使える🛡️

5) テク③:アサーション関数で「ここから先は安全」宣言🚨✅

ときどき「条件を満たさないなら例外で止めたい」場面があるよね🔥 そういうときは assertion functions が便利! (TypeScriptの asserts を使うやつ)(TypeScript)

type User = { id: string } | null;

function assertLoggedIn(user: User): asserts user is { id: string } {
if (user === null) throw new Error("ログインしてください🙇‍♀️");
}

function loadMyPage(user: User) {
assertLoggedIn(user);
// ここから先 user は { id: string } に確定✨
return `loading... userId=${user.id}`;
}

使いどころは👇

  • 「nullなら止める」
  • 「想定外なら止める」
  • 「ここから先は絶対この型!」ってしたいとき💡

6) テク④:条件が増えるなら「判別可能Union+switch+網羅チェック」🧱✨

ifが増える理由って、だいたい 状態(ステータス)が増えるからなの🥹 そこで効くのがこれ👇

  • 判別可能(discriminated)Union
  • switch で分岐
  • never を使った 網羅チェック(漏れたらコンパイルで怒られる)💥(TypeScript)
type OrderStatus =
| { type: "PAID" }
| { type: "SHIPPED"; trackingNo: string }
| { type: "CANCELLED"; reason: string };

function assertNever(x: never): never {
throw new Error("Unhandled case: " + JSON.stringify(x));
}

function getStatusLabel(s: OrderStatus) {
switch (s.type) {
case "PAID":
return "支払い完了💳✨";
case "SHIPPED":
return `発送済み📦✨ 追跡: ${s.trackingNo}`;
case "CANCELLED":
return `キャンセル😢 理由: ${s.reason}`;
default:
return assertNever(s); // ← ここが網羅チェック🔥
}
}

これの強さ👇

  • ステータスが増えたとき、対応漏れがコンパイルで発覚する💥
  • 「どんな状態があるか」が型で見える👀✨
  • 条件が散らばりにくい(switchに集まる)📌

7) どこに置く?おすすめ最小ファイル構成📁✨

「条件のDRY」は、置き場所を決めるとさらに強いよ💪

  • src/domain/rules/:ビジネスルール(判定)
  • src/domain/guards/:型ガード・アサーション
  • src/ui/:画面側(なるべく判定を書かない)

例👇

src/
domain/
rules/
shipping.ts
discount.ts
guards/
userGuards.ts
ui/
cartView.ts

ルールは UIから剥がすと、マジで散らばりが止まる🧼✨


8) ミニ演習🎓✨:「同じifが3回ある」→「判定を1回に」🎯

お題:送料無料と割引(わざと散らしてある)😈

type User = { isMember: boolean };
type Cart = { subtotal: number };

function calcA(user: User, cart: Cart) {
if (user.isMember && cart.subtotal >= 5000) {
return cart.subtotal; // shipping 0
}
return cart.subtotal + 500;
}

function showBadge(user: User, cart: Cart) {
if (user.isMember && cart.subtotal >= 5000) {
return "送料無料🎉";
}
return "";
}

function logShipping(user: User, cart: Cart) {
if (user.isMember && cart.subtotal >= 5000) {
console.log("shipping:0");
} else {
console.log("shipping:500");
}
}

ステップ1:条件を関数にする🛡️

function isFreeShipping(user: User, cart: Cart) {
return user.isMember && cart.subtotal >= 5000;
}

ステップ2:全部それを使う✨

function calcA(user: User, cart: Cart) {
return isFreeShipping(user, cart) ? cart.subtotal : cart.subtotal + 500;
}

function showBadge(user: User, cart: Cart) {
return isFreeShipping(user, cart) ? "送料無料🎉" : "";
}

function logShipping(user: User, cart: Cart) {
console.log(isFreeShipping(user, cart) ? "shipping:0" : "shipping:500");
}

💡ここで大事なのは 「同じ条件式」を消すことより、「同じルール(意味)」を1か所にすることだよ😊✨


9) AI活用コーナー🤖💖(“丸投げ禁止”の上手い使い方)

AIは「候補出し」が超得意✨ でも「採用判断」はあなたがやるのがコツ🧠

使えるお願い文(コピペOK)💌

  • 「このファイル内で、同じ条件式が繰り返されてる箇所を列挙して、1つの関数にまとめる案を出して」
  • 「この条件式に“意味が分かる名前”を10個出して。短くて読みやすいのがいい」
  • 「ユニオン型にしてswitchで網羅チェックできる形に変えて」
  • 「この判定関数に対するテスト観点を箇条書きで出して(境界値・例外・組み合わせ)」

GitHub Copilot側にも「タスクの文脈をまとめて使う」系の機能が増えてきてるので、ルールの説明文(仕様)を一緒に渡すと精度が上がるよ📌(GitHub Docs)


10) まとめ🎀✨(第6章で覚える合言葉)

  • 同じifが増えたら、それは同じルールが散らばってるサイン🚨
  • ✅ まずは 条件に名前(変数)📛
  • ✅ 本命は 判定関数/ガード関数で1か所化🛡️
  • ✅ 状態が増えるなら discriminated union+switch+網羅チェック🧱

次の章(第7章)は「try/catchコピペ地獄」脱出編🚨🧯 条件を片づけられた今、エラーも同じノリでキレイにできるよ〜😆💖