第08章:設計の“匂い”を嗅ぐ(つらさのサイン集)👃
この章でできるようになること 🎯
- 「なんかこのコードつらい…😵💫」を言葉にできるようになる
- “匂い”を見つけたら、いきなり大改造せずに小さく直す手順がわかる
- 次の章(判断フロー🧭)で「どのパターンが効きそう?」につなげられるようになる
匂いってなに?(バグじゃない)🌫️
“匂い”は 今すぐ壊れてはいないけど、このまま育つと将来つらくなりやすい「サイン」だよ〜👀💦 たとえば…
- 追加要件が来るたびに if が増えていく😇
- 似たコードをコピペして修正漏れが起きる😱
- どこを直せばいいか探すだけで疲れる🫠
匂いは「悪」じゃないよ!🙆♀️ 小さいコードなら許せることもあるし、**“規模”と“変化の頻度”**で判断するのがコツ✨
1. コードの不吉な匂い(Code Smells)🐽💦

「動くけど、なんか嫌な感じがする…」
匂いの見つけ方 3ステップ 🧭🔍
- 困った瞬間をメモする📝 「追加が怖い」「読むのがしんどい」「修正が連鎖する」みたいに感情でOK😂
- どこが原因っぽいか場所を特定する📍 ファイル?関数?条件分岐?引数?依存?
- “小さい改善”だけ入れてみる✂️ リネーム、関数抽出、重複削除、早期return…まずこの辺で十分💕
代表的な匂いカタログ(初心者向け・超実用)🧀✨
匂い① if / switch が太り続ける 🐷🔀
サイン👀
- 分岐が3〜4個を超えて、スクロールが増える
- 「この条件、どこで使ってたっけ?」が頻発
- 追加仕様が来るたびに case が増える
つらさ😭
- 修正漏れ・条件抜けが起きやすい
- テストを書かないと安心できない
まずの一手✂️(パターン前)
-
条件を“名前付きの関数”にする(読みやすくする)
isHappyHour(order)みたいに命名して意味を固定🧠
-
早期returnでネストを浅くする
-
switchの中身を小関数へ分割して“太り”を止める
この後に効きやすい候補🧩
- 「やり方を差し替えたい」→ Strategy
- 「状態でできることが変わる」→ State(まず判別Union)
匂い② 引数が増殖して呪文になる 📿😵💫
サイン👀
- 引数が4個以上で、呼び出し側が読めない
true, false, 0, "A"みたいな“謎の並び”が出る- ちょっと追加するたびに全呼び出し修正が必要
つらさ😭
- 呼び出しミスが増える(順番間違い・意味違い)
- 変更に弱い(新項目追加で破壊的)
まずの一手✂️
-
オプションオブジェクトにする(TypeScriptの定番✨)
{ size, sugar, coupon }みたいに名前で渡す
-
既定値(デフォルト)を関数内で持つ
この後に効きやすい候補🧩
- 「手順や順番が大事」→ Builder
- 「種類が増える」→ Factory(登録型も)
匂い③ コピペが増えて“微妙に違う同じ処理”が乱立 📄📄📄
サイン👀
- 似た関数が複数ある(名前だけ違う)
- 片方だけ修正されて差が出る
- “似てるけど少し違う”が増えて混乱
つらさ😭
- 修正漏れが発生する
- バグの温床になる
まずの一手✂️
- 共通部分を関数に切り出す(Extract Method)
- “差分”を引数(または関数)で渡す
- まずは 1箇所だけ共通化して、効果を体感しよ🙌
匂い④ 名前がふわっとしてて脳内補完が必要 🤯🌀
サイン👀
doStuff,handle,data,tmp,calc2が多い- “何を/なぜ”が読めない
- コメントがないと理解できない
つらさ😭
- 読む人の解釈がブレる
- 仕様変更で破綻しやすい
まずの一手✂️
-
「何を返す?」「いつ使う?」で名前を決める
getTotal(合計を返す)applyCoupon(クーポンを適用する)
-
“単位”も入れる(
priceYenみたいに)💰
匂い⑤ 1つの関数が全部やってる(神関数)👑💥
サイン👀
- 1関数が50行超え
- DB/計算/整形/ログ…が混在
- 途中で例外やreturnが多すぎる
つらさ😭
- テストしづらい
- ちょい修正で壊れやすい
まずの一手✂️
-
“段落”ごとに関数抽出(検証→計算→整形…)
-
I/O(外部)と純粋計算(内部)を分ける
- 純粋計算はテストが超ラク🥳
匂い⑥ 依存が絡まりすぎて“1個直すと全部動く”🕸️😱
サイン👀
- ある関数がいろんなモジュールを直接importしてる
- 変更の影響範囲が読めない
- テストで差し替えできない
つらさ😭
- “部分修正”ができず、常に全体修正になる
- リファクタが怖くなる
まずの一手✂️
- まずは「境界」を決める(外部API・日時・乱数・入出力など)
- 依存は引数で渡す(DIっぽく)
- interface(もしくは関数型)で“差し替え口”だけ作る
匂い⑦ マジックナンバー / マジック文字列が散らばる 🪄🔢
サイン👀
1.1や"HAPPY"みたいな値があちこち- 変更が来ると検索しまくり
つらさ😭
- “意味の取り違え”が起きる
- 変更が漏れる
まずの一手✂️
- 定数化する(
const TAX_RATE = 0.1) - 文字列は union で型にする(
type Coupon = "happy" | "member")
匂い⑧ 例外で流れを作っていて、分岐が読めない 💥🧯
サイン👀
- 例外が“正常ルート”みたいに使われてる
- try/catch があちこちにある
つらさ😭
- 「どこで失敗する?」が読めない
- ログやUI表示が崩れる
まずの一手✂️
- 失敗は戻り値で表す(Result的)
- 例外は“境界”(外部I/O)中心に寄せる
ハンズオン:カフェ注文コードに“匂いコメント”を貼ろう 📝☕✨
Step 1:わざと匂うコードを読む👃💦
(※このコードは“匂い探し用”にわざと詰め込んでるよ!)
// order.ts
type Size = "S" | "M" | "L";
type Coupon = "none" | "happy" | "member";
type OrderItem = {
name: string;
basePrice: number; // 税抜
size: Size;
sugar: number; // 0〜5
coupon: Coupon;
};
export function calcTotal(items: OrderItem[], isTakeout: boolean, nowHour: number): number {
let total = 0;
for (const item of items) {
let price = item.basePrice;
// サイズで加算
if (item.size === "M") price += 50;
else if (item.size === "L") price += 100;
// 砂糖(無料だけど上限チェック…みたいな仕様が混ざる)
if (item.sugar < 0) throw new Error("invalid sugar");
if (item.sugar > 5) throw new Error("too much sugar");
// クーポン
if (item.coupon === "happy") {
// 夕方だけ10%OFF
if (nowHour >= 16 && nowHour <= 18) {
price = Math.floor(price * 0.9);
}
} else if (item.coupon === "member") {
// 会員は常時5%OFF
price = Math.floor(price * 0.95);
}
// 持ち帰りは容器代
if (isTakeout) price += 30;
// 税(10%)
price = Math.floor(price * 1.1);
total += price;
}
return total;
}
Step 2:匂いをコメントで“見える化”🧷👀
次を目標に、コードにコメントを入れてみてね👇
- どこが「if太り」?🐷
- 引数(
items, isTakeout, nowHour)は今後増えそう?📿 - “仕様”がどこに散ってる?(税、容器代、割引…)🌀
- 例外の使い方は自然?🧯
例(こんな感じでOK🙆♀️)
// 匂い①:クーポン分岐が増えたら地獄になりそう// 匂い②:税率1.1がマジックナンバー
ちょい改善:パターン無しで“読みやすく”する ✂️✨
改善A:マジック値に名前をつける🪄➡️📛
const TAX_RATE = 0.1;
const TAKEOUT_FEE = 30;
function applyTax(price: number): number {
return Math.floor(price * (1 + TAX_RATE));
}
改善B:「割引」と「価格計算」を段落で分ける📚
function sizeExtra(size: Size): number {
if (size === "M") return 50;
if (size === "L") return 100;
return 0;
}
function validateSugar(sugar: number): void {
if (sugar < 0) throw new Error("invalid sugar");
if (sugar > 5) throw new Error("too much sugar");
}
function applyCoupon(price: number, coupon: Coupon, nowHour: number): number {
if (coupon === "member") return Math.floor(price * 0.95);
if (coupon === "happy") {
const isHappyHour = nowHour >= 16 && nowHour <= 18;
if (isHappyHour) return Math.floor(price * 0.9);
}
return price;
}
export function calcTotal(items: OrderItem[], isTakeout: boolean, nowHour: number): number {
let total = 0;
for (const item of items) {
validateSugar(item.sugar);
let price = item.basePrice + sizeExtra(item.size);
price = applyCoupon(price, item.coupon, nowHour);
if (isTakeout) price += TAKEOUT_FEE;
total += applyTax(price);
}
return total;
}
この時点で、まだGoFパターンは入れてないのに…
- どこで何してるか見えやすい😍
- 追加仕様(税率変更、クーポン追加)に備えやすい🧠 って感じになるよ〜✨
VS Codeでやると速い操作 🧰⚡
- Rename Symbol(F2):名前の改善が一瞬でできる🔁
- Find All References:影響範囲を把握できて安心👀
- Extract Method / Extract Variable:神関数を分割していける✂️ VS Codeはこういうリファクタ機能を標準でサポートしてるよ。(Visual Studio Code)
AIに頼むときの“ちょうどいい”投げ方 🤖💬✨
匂い指摘(まずは観察役)
このTypeScriptコードの「設計の匂い」を5つ挙げて。
- 匂いの種類(例:if肥大化 / 重複 / 命名 / 依存 など)
- なぜ辛くなるか
- “パターンを入れずに”まずできる小さな改善
で出して。
改善を“段階的に”(一気に改造させない)
改善案を「小→中」の順で3段階にして。
制約:
- TypeScriptの標準機能や一般的な書き方(関数分割、定数化、オプションオブジェクト)中心
- デザインパターン専用の独自クラス乱立は禁止
- まずは挙動を変えないリファクタだけ
テスト案も一緒に(安心材料🧪)
この関数のテストケース案を10個。
代表ケース+境界ケース(割引の時間、サイズ、持ち帰り、砂糖上限など)を混ぜて。
壊してない証拠:テストで守る 🧪🛡️
Nodeには組み込みのテストランナーがあるので、最小ならそれでOKだよ。(nodejs.org)
// order.test.ts
import test from "node:test";
import assert from "node:assert/strict";
import { calcTotal } from "./order.js";
test("member coupon applies 5% off (rough check)", () => {
const total = calcTotal(
[{ name: "Latte", basePrice: 500, size: "S", sugar: 2, coupon: "member" }],
false,
12
);
assert.ok(Number.isFinite(total));
});
テストは最初から完璧じゃなくていいよ〜🙆♀️💕 「匂いを直したけど、動きは同じ」が守れれば勝ち🎉
ミニ最新メモ(2026年2月時点)📌✨
- TypeScript は 5.9系の安定版リリースが確認できるよ。(GitHub)
- TypeScript のネイティブ実装(Go移植)関連の動きがあり、2026年初頭リリース目標という報道・告知も出てるよ。(InfoWorld)
- Node.js は公式のリリース表で、Current が v25、Active LTS が v24(2026-02時点の更新情報)になってる。(nodejs.org)
structuredClone()は Web 標準として広く利用可能(互換性情報あり)だよ。(MDNウェブドキュメント)
よくあるつまずき 🐣💥
- 匂いを見つけた瞬間、全部直したくなる → まず“1個だけ”でOK🧁
- パターンを先に当てたくなる → 先に「困りごと」を言語化するのが近道🧭
- リファクタで壊れた気がする → テスト(or 最小の動作確認)を“先に”作る🧪
まとめ ✅🎀
- 匂いは「将来つらくなるサイン」🌫️
- まずは 見える化 → 小さい改善 → テストで守る の3点セット👃✂️🧪
- if太り・引数増殖・重複・命名あいまい…この辺を嗅げるだけで、次の章から一気に楽になるよ〜🥳✨