第9章:まとめプロジェクト(DRY改善を1周する)🎯🛠️🎉
この章は「DRYを“知識”じゃなくて“手順”として身につける」回だよ〜!💖 カート計算(割引・送料・クーポン)を、**重複だらけ(WET)→ すっきり(DRY)**に直していくよ🧹✨
0) 最新メモ(2026/01/12時点)🗓️✨
- TypeScript は 5.9 系が “latest” 扱いとして案内されてるよ📌 (TypeScript)
- VS Code は December 2025 (version 1.108) が 2026/01/08 リリースとして出てるよ🧡 (Visual Studio Code)
- Copilot は **Chat(VS Code内のチャット)**や、**agent mode(タスクを自律的に進める)**みたいな機能が公式に案内されてるよ🤖✨ (Visual Studio Code)
- TypeScript は 5.9 の次に 6.0 → 7.0 を見据えた話も出てる(方針の話)よ🔭 (Microsoft for Developers)
1) この章のゴール💪🎯

最終的に、あなたができるようになること👇✨
- 重複を見つけて「種類」を言える🏷️👀
- テストで守りながら安全にまとめられる🧪🛡️
- “まとめた結果”が読みやすくなるように整えられる📚✨
- 「あえて残す重複」も、理由を書いて判断できる📝🌿
2) 題材:カート計算(割引・送料・クーポン)🛒💳
よくあるやつ👇
- 小計 subtotal 💰
- 会員ランク割引(例:Goldは10%)👑
- クーポン(例:WELCOMEで500円引き、FREESHIPで送料0)🎫
- 送料(例:500円、ただし一定額以上で無料)🚚
- 例外(不正クーポン、期限切れ、金額がおかしい…)🚨
ここ、重複が爆増しがちでDRYの練習に最高なの🥹✨
3) プロジェクトの形(完成イメージ)🏗️✨
最終的にファイルはこんな雰囲気にするよ👇
src/constants.ts… 文字列・数値の定義を1か所へ🏷️src/types.ts… 型をDRYに整理🧱src/guards.ts… 条件チェック(isXxx)を集約🛡️src/pricing.ts… 小計/割引/送料の計算を分ける🧮src/coupons.ts… クーポン処理を分離🎫src/result.ts… 成功/失敗の形を統一✅❌src/checkout.ts… 司令塔(読みやすい!)🎮✨
4) まずは“WETな初期コード”を用意しよう😈📄
下のコードは「わざと」重複まみれだよ!安心してね😂
src/checkout.wet.ts(スタート地点)
type MemberRank = "GUEST" | "SILVER" | "GOLD";
type CouponCode = "WELCOME" | "FREESHIP" | "VIP" | "NONE";
type CartItem = {
sku: string;
name: string;
price: number;
qty: number;
};
type CheckoutInput = {
items: CartItem[];
memberRank: MemberRank;
coupon: CouponCode;
};
export function checkoutWet(input: CheckoutInput) {
// ❶ 小計計算(このあと別の場所でも似たのが出る…)
let subtotal = 0;
for (const it of input.items) {
if (it.qty <= 0) throw new Error("qty must be > 0");
if (it.price < 0) throw new Error("price must be >= 0");
subtotal += it.price * it.qty;
}
// ❷ 会員割引(ifが増えがち…)
let memberDiscount = 0;
if (input.memberRank === "GOLD") {
memberDiscount = Math.floor(subtotal * 0.1);
} else if (input.memberRank === "SILVER") {
memberDiscount = Math.floor(subtotal * 0.05);
} else {
memberDiscount = 0;
}
// ❸ クーポン割引(WELCOME/VIPの計算がベタ書き)
let couponDiscount = 0;
if (input.coupon === "WELCOME") {
couponDiscount = 500;
} else if (input.coupon === "VIP") {
couponDiscount = Math.floor(subtotal * 0.15);
} else {
couponDiscount = 0;
}
// ❹ 送料(条件があちこちに増えるやつ)
let shipping = 500;
if (subtotal - memberDiscount - couponDiscount >= 5000) {
shipping = 0;
}
if (input.coupon === "FREESHIP") {
shipping = 0;
}
// ❺ 合計(似た式が増殖しがち)
const total = Math.max(0, subtotal - memberDiscount - couponDiscount) + shipping;
return {
subtotal,
memberDiscount,
couponDiscount,
shipping,
total,
message: "ok",
};
}
5) 手順①:まず“テスト”で安全網を張る🧪🛡️
DRY改善はコードを動かしながら直すのが勝ち!✨ 最初に「今の挙動」をテストで固定しちゃおう📌
テスト観点(最低これだけでOK)✅
- 通常ケース:GUEST + NONE
- 会員割引:SILVER / GOLD
- クーポン:WELCOME / VIP / FREESHIP
- 境界:送料無料ライン 5000 の前後
- エラー:qty <= 0 / price < 0
AI活用🤖💡(コピペ用プロンプト)
- 「この関数に対して、境界値を含むテストケースを10個列挙して。期待値も一緒に」
- 「送料無料条件と割引の組み合わせでバグりやすいパターンを教えて」
6) 手順②:重複に“ラベル”を貼る🏷️👀
WETコードを見ながら、付箋を貼る気持ちでやるよ〜!💖
ラベル例(第2章の復習)
- 📄コードの重複:小計計算や合計式がコピペされそう
- 🧠ルールの重複:送料無料条件、割引率、丸め方
- 🏷️データの重複:
500,5000,"GOLD"など散らばる - 🚨例外の重複:qty/priceのチェック、エラーメッセージ
ここでのコツは👇 **「同じ理由で変わる?」**を自分に聞くことだよ🧠✨ (例:送料無料ラインが変わったら、何か所直す?😱)
7) 手順③:関数抽出&引数化(第3章)✂️🧩
最初の勝ち筋はこれ! 「小計」「割引」「送料」を関数に分けるだけで一気に読みやすくなるよ📚✨
目標
calcSubtotal(items)calcMemberDiscount(subtotal, rank)calcCouponDiscount(subtotal, coupon)calcShipping(net, coupon)← “net” は割引後金額みたいな名前にするのがオススメ💡
AI活用🤖💬
- 「今のコードを、責務ごとに関数に分割して。命名は読みやすさ重視で3案」
- 「関数分割した後に、引数が多すぎるならどう整理する?」
8) 手順④:定数・対応表・ユニオン型(第4章)🏷️🗂️
ここで “魔法の数字/文字列” を卒業するよ🎓✨
まとめたいもの例
- 送料:
500 - 送料無料ライン:
5000 - 割引率:GOLD 0.1 / SILVER 0.05
- クーポンの定義:WELCOME 500 / VIP 0.15 / FREESHIP special
ポイント💡
- 数字を1か所に集めると、仕様変更が秒速になる🚀
- クーポンは
Record<CouponCode, ...>で “対応表” にするとキレイ🗂️
9) 手順⑤:TypeScriptで“型のDRY”(第5章)🧠🧱
このプロジェクト、完成版ではだいたいこうなる👇
- 入力:
CheckoutInput - 出力:
CheckoutSummary - 計算途中:
PricingBreakdown - エラー:
CheckoutError
DRYのコツ
- 似た型が増えたら、まず「共通の核」を作る🧩
Pick/Omitで用途別の形にする✨
AI活用🤖
- 「この型、将来 “配送先” が追加されても崩れにくい設計にして」
- 「Pick/Omitで整理できるところを提案して」
10) 手順⑥:条件分岐のDRY(第6章)🛡️🌿
ifが散らばると、仕様が“分裂”するのが怖い😵💫 だから 判定はガード関数へ✨
例:
isFreeShipping(net, coupon)isValidCoupon(coupon)(将来、期限や対象SKUを足すときに効く💖)isVipEligible(memberRank)とかもアリ👑
11) 手順⑦:エラー処理のDRY(第7章)🚨🧯
この題材だと、エラーはよくこうなる👇
- qtyが0以下
- priceが負
- クーポンが不正(今回は union だから少ないけど、現実は文字列入力が多い😭)
おすすめはどっちか(好きな方でOK)🍀
- A)
throwするなら:CheckoutErrorを作って整形を1か所へ - B)
Resultっぽく返すなら:{ ok: true, value } | { ok:false, error }に統一✅❌
Copilotの agent mode を使うなら、 「CheckoutError導入→置換→テスト通す」みたいにタスク単位で任せやすいよ🤖✨ (GitHub Docs)
12) 手順⑧:あえて“消さない重複”もOK(第8章)🐙⚠️📝
ここが第9章のいちばん大事ポイントかも💖
例:残してOKな重複
- 1〜2行の式が、読んだ方が早いとき(無理に関数化しない)
- 呼び出し側が
calcA(calcB(calcC(...)))みたいに暗号になるとき - “似てるけどルールが違う” もの(将来別々に変わりそう)
ミニ提出物(自分用でOK)📄✨
DRY_DECISIONS.md を作って、こう書く👇
- 残した重複:〇〇
- 残した理由:変更理由が別/読みやすさ優先/今は早い抽象化、など
13) 最終チェックリスト✅🎉
最後にこれを満たしたらクリアだよ〜!💮💖
- 仕様変更(例:送料無料ライン)を 1か所直せばOKになった?🔧
- 割引率やクーポン定義が 散らばってない?🏷️
- “判定” と “処理” が分かれてて読める?👀
- テストがあって、安心して触れる?🧪
- 残した重複に理由が書けてる?📝✨
14) AI活用チートシート🤖💖(そのまま投げてOK)
- 「このファイルの重複を、コード/ルール/データ/例外に分類して一覧化して」
- 「関数抽出の候補と、引数化の設計案を3つ。読みやすさ重視で」
- 「定数化すべき値を全部列挙して。命名案も」
- 「ガード関数にすべき条件と、その関数名案を出して」
- 「リファクタ後も挙動が変わってないことを保証するテストケースを追加して」
- 「“やりすぎ抽象化”になってる箇所があれば指摘して」
まとめ🎀✨
第9章でやったのは、これ👇 見つける🏷️ → 守る🧪 → 分ける✂️ → 集める🏷️ → 型で固める🧱 → 判定をまとめる🛡️ → 例外を整える🚨 → 判断を書く📝
ここまで1周できたら、DRYはもう “実戦スキル” だよ〜!🎉💖
もしよければ次は、あなたが作りたい題材(注文管理・在庫・ユーザー権限とか)にこの第9章の手順を“そのまま移植”した版も作れるよ🛠️✨