Skip to main content

第27章:Builder ④ まとめ演習:読みやすさチェック(レビュー観点)👀

ねらい🎯

この章を終えると、こんな判断ができるようになります😊✨

  • 「引数の並び」🧵 / 「オプションオブジェクト」📦 / 「Builder」🧱 を 状況で使い分けできる
  • PRレビューで「読みやすいAPIか?」を チェックリストで指摘できる✅
  • 3案を 同じ題材で比較して、チームで“決め”を作れる🫶💡

お題:カフェ注文を “気持ちよく” 作りたい☕🧁

「注文を作る」って、最初は単純でもすぐこうなりがち👇😵‍💫

  • 必須が増える(ドリンク/サイズ/温冷…)
  • 任意も増える(ミルク/砂糖/トッピング/メモ…)
  • さらに増える(テイクアウト、配達、予約時間…)

ここで 呼び出し側が読めない と、設計が崩れます🫠 だから今日は「呼び出し側(使う側)が気持ちいいか?」を主役にします👑✨


まずは共通の型(ドメイン)を用意しよう🧩

※ この章は“比較”が目的なので、型はシンプルでOKです😊

// domain.ts
export type Drink = "latte" | "americano" | "tea";
export type Size = "S" | "M" | "L";
export type Temperature = "hot" | "iced";
export type Milk = "none" | "regular" | "soy" | "oat";
export type Topping = "cinnamon" | "whip" | "caramel";

export type Order = {
id: string;
drink: Drink;
size: Size;
temp: Temperature;

milk: Milk;
sugar: number; // 0〜3想定
toppings: Topping[];

takeout: boolean;
note?: string;
scheduledAt?: Date;
};

let seq = 0;
export const resetOrderIdForTest = () => { seq = 0; };
export const nextOrderId = () => `o_${++seq}`;

3つの作り方で “同じ注文” を作って比較🆚✨

3つの手法(引数、オプション、Builder)の比較。

A案:引数の並び(positional args)🧵😵‍💫

最初はラク。でも、すぐ辛くなりやすい代表です…!

// createOrder_positional.ts
import { Milk, Order, Temperature, Drink, Size, Topping, nextOrderId } from "./domain";

export function createOrderPositional(
drink: Drink,
size: Size,
temp: Temperature,
milk: Milk,
sugar: number,
toppings: Topping[],
takeout: boolean,
note?: string,
scheduledAt?: Date,
): Order {
return {
id: nextOrderId(),
drink,
size,
temp,
milk,
sugar,
toppings,
takeout,
note,
scheduledAt,
};
}

呼び出し側👇(読める?😇)

createOrderPositional(
"latte",
"M",
"hot",
"oat",
1,
["cinnamon", "whip"],
true,
"急ぎで!",
new Date("2026-02-04T10:00:00"),
);

つらいポイント😵‍💫💥

  • "oat"1true何の値? ってなる
  • 引数追加で 呼び出しが全部壊れやすい
  • 並び間違いが レビューで見落ちやすい(型が同じだと特に😱)

B案:オプションオブジェクト(TSの王道🥇)📦✨

TypeScriptで「引数が増えたらまずこれ」になりやすい形です😊 読む人に優しいのが強い💪

// createOrder_options.ts
import { Drink, Size, Temperature, Milk, Topping, Order, nextOrderId } from "./domain";

export type CreateOrderOptions = {
drink: Drink;
size: Size;
temp: Temperature;

milk?: Milk;
sugar?: number;
toppings?: readonly Topping[];

takeout?: boolean;
note?: string;
scheduledAt?: Date;
};

export function createOrderOptions(opts: CreateOrderOptions): Order {
const {
drink,
size,
temp,
milk = "regular",
sugar = 0,
toppings = [],
takeout = false,
note,
scheduledAt,
} = opts;

return {
id: nextOrderId(),
drink,
size,
temp,
milk,
sugar,
toppings: [...toppings],
takeout,
note,
scheduledAt,
};
}

呼び出し側👇(めっちゃ読みやすい〜!🥹✨)

createOrderOptions({
drink: "latte",
size: "M",
temp: "hot",
milk: "oat",
sugar: 1,
toppings: ["cinnamon", "whip"],
takeout: true,
note: "急ぎで!",
scheduledAt: new Date("2026-02-04T10:00:00"),
});

さらにレビュー向きの小ワザsatisfies)🪄✨ 「余計なキー」「typo」を減らしやすいです😊

import { CreateOrderOptions, createOrderOptions } from "./createOrder_options";

const opts = {
drink: "latte",
size: "M",
temp: "hot",
milk: "oat",
sugar: 1,
toppings: ["cinnamon", "whip"],
takeout: true,
} satisfies CreateOrderOptions;

createOrderOptions(opts);

いいところ😍

  • 呼び出しが “文章” になる📖✨
  • 任意/必須が見える👀
  • 引数追加しても壊れにくい🧱

注意点⚠️

  • オプションが増えすぎると「何を渡すべき?」で迷子になる🌀 → その時に Builder を検討!

C案:Builder(呼び出しを “会話” にする)🧱✨

「オプション多すぎ問題」や「順番が大事」に寄せたい時に強いです💪 ※ この章は“過剰な独自クラス”を避けたいので、クラスではなく関数+オブジェクトで書きます😊

// createOrder_builder.ts
import { Drink, Size, Temperature, Milk, Topping, Order, nextOrderId } from "./domain";

export function orderBuilder() {
const draft: Omit<Order, "id"> = {
drink: "latte", // 仮の初期値(必須は後で上書き想定)
size: "M",
temp: "hot",
milk: "regular",
sugar: 0,
toppings: [],
takeout: false,
};

return {
drink(v: Drink) { draft.drink = v; return this; },
size(v: Size) { draft.size = v; return this; },
temp(v: Temperature) { draft.temp = v; return this; },

milk(v: Milk) { draft.milk = v; return this; },
sugar(v: number) { draft.sugar = v; return this; },
addTopping(v: Topping) { draft.toppings.push(v); return this; },

takeout(v: boolean = true) { draft.takeout = v; return this; },
note(v: string) { draft.note = v; return this; },
scheduleAt(v: Date) { draft.scheduledAt = v; return this; },

build(): Order {
return { id: nextOrderId(), ...draft, toppings: [...draft.toppings] };
},
};
}

呼び出し側👇(読みやすいし、補完も気持ちいい🫶✨)

import { orderBuilder } from "./createOrder_builder";

const order = orderBuilder()
.drink("latte")
.size("M")
.temp("hot")
.milk("oat")
.sugar(1)
.addTopping("cinnamon")
.addTopping("whip")
.takeout()
.note("急ぎで!")
.scheduleAt(new Date("2026-02-04T10:00:00"))
.build();

いいところ😍

  • 呼び出しが “意図の列” になる🧾✨
  • オプションが多くても、補完で迷いにくい🧭
  • “段階的に構築”が自然(章26の流れと相性◎)🧱

注意点⚠️

  • Builder自体のコード量は増える(作る側コスト)💸
  • 「必須をセットし忘れる」を型で完全に防ぐには、もう一段工夫がいる(発展)🧠

どれが読みやすい?レビュー観点チェックリスト✅👀

PRレビューでそのまま使える形にします💌✨

1) 呼び出し1行で意図が読める?📖

  • A案:値だけ並ぶ → “翻訳”が必要😵
  • B案:キーが語る → 読める😍
  • C案:メソッド名が語る → 読める😍

2) 必須/任意が自然に分かる?🔎

  • B案:必須は型で強制、任意は ? で明確✨
  • C案:必須を「順番」や「build前に揃える」で表現しやすい🧱

3) 変更に強い?(引数追加で壊れない?)🛡️

  • A案:壊れやすい💥
  • B案:壊れにくい✨
  • C案:壊れにくい✨(ただしBuilder側の修正は必要)

4) レビューでミスを見つけやすい?🧑‍⚖️

  • A案:並びミスを見落としやすい😱
  • B/C案:名前が見えるので気づきやすい👀✨

5) “補完”で迷子にならない?🧠

  • B案:オブジェクト内で候補が出る📦
  • C案:チェーンで候補が出る🧱 (どっちも強い!)

6) 差分(diff)が読みやすい?🧾✨

  • B案:追加行が綺麗に出る(レビューしやすい)
  • C案:チェーンの増減が追える
  • A案:差分は出るけど “意味” が見えにくい

演習①:同じ注文を3案で作って“目で比較”しよう🧁👀

次の注文を、A/B/C で全部書いて並べてください😊✨

  • drink: latte
  • size: M
  • temp: hot
  • milk: oat
  • sugar: 1
  • toppings: cinnamon + whip
  • takeout: true
  • note: 急ぎで!
  • scheduledAt: 2026-02-04 10:00

最後に自分で一言メモ📝✨

  • 「どれが一番 “誤読されない”?」
  • 「チームで採用するならどれ?」

演習②:レビューコメント練習📝💬(テンプレ付き)

PRでこう書けると強いです💪✨(そのままコピペOK)

  • 「引数の並びだと意図が読みにくいので、オプションオブジェクトにしたいです」 → “どこが読みにくいか” を具体的に(例:true が何か分からない)

  • 「オプションが増えてきたので、Builderにすると補完で迷いにくくなりそうです」 → “増え方” を根拠にする(今後も増える見込み)

  • 「デフォルト値が分散してるので、作成関数の中で一箇所に寄せたいです」 → デフォルトは “仕様” なので散るとバグりやすい😵‍💫


演習③:3案が同じ結果になるテスト🧪⚡

テストは「呼び出しの読みやすさ」を守る盾です🛡️✨ ここでは Vitest を例にします(2025年にVitest 4.0が発表されています)。(Vitest)

// createOrder.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { resetOrderIdForTest } from "./domain";
import { createOrderPositional } from "./createOrder_positional";
import { createOrderOptions } from "./createOrder_options";
import { orderBuilder } from "./createOrder_builder";

beforeEach(() => resetOrderIdForTest());

describe("create order variations", () => {
it("A/B/C produce same Order shape (except id sequence is deterministic here)", () => {
const a = createOrderPositional(
"latte",
"M",
"hot",
"oat",
1,
["cinnamon", "whip"],
true,
"急ぎで!",
new Date("2026-02-04T10:00:00"),
);

resetOrderIdForTest();

const b = createOrderOptions({
drink: "latte",
size: "M",
temp: "hot",
milk: "oat",
sugar: 1,
toppings: ["cinnamon", "whip"],
takeout: true,
note: "急ぎで!",
scheduledAt: new Date("2026-02-04T10:00:00"),
});

resetOrderIdForTest();

const c = orderBuilder()
.drink("latte")
.size("M")
.temp("hot")
.milk("oat")
.sugar(1)
.addTopping("cinnamon")
.addTopping("whip")
.takeout()
.note("急ぎで!")
.scheduleAt(new Date("2026-02-04T10:00:00"))
.build();

expect(a).toEqual(b);
expect(b).toEqual(c);
});
});

AIプロンプト例🤖💬(レビュー目線を鍛える用)

1) 「読みやすさレビュー」をAIにやってもらう👀✨

次の3つのAPI(引数並び / オプションオブジェクト / Builder)を比較して、
「呼び出し側の可読性」「誤用しにくさ」「変更に強いか」の観点でレビューしてください。
- それぞれの欠点を、具体的に“どんなバグ/誤読が起きるか”まで書く
- 改善案は「TypeScript標準の型・構文」を中心に(余計な独自クラスを増やさない)
題材: カフェ注文 createOrder
コード: <ここに貼る>

2) 「チームの決め」を作る🫶📌

このプロジェクトでは createXxx 系の関数が増えます。
引数が増えた時のルール(例: 3個までならpositional、それ以上はoptions、順序依存や大量オプションならbuilder…)を、
初心者にも守れる形で提案してください。レビューで使えるチェックリストも付けてください。

3) Builderが過剰か判断する🧠⚖️

このBuilderは過剰設計か評価してください。
- オプションオブジェクトで十分な場合の条件
- Builderが妥当になる条件(順序・段階的構築・補完の価値など)
- それぞれの「保守コスト」を具体例で説明
コード: <ここに貼る>

よくあるつまずき&回避💡😇

  • **「positionalのまま粘って地獄」**😵‍💫 → 引数が増えたら、まず **B案(オプション)**に逃げてOK📦✨

  • **「オプションが増えすぎて、何を渡せばいいか分からない」**🌀 → その時が C案(Builder)検討タイミング🧱✨(補完の価値が出る)

  • **「デフォルトがあちこちに散る」**🧨 → デフォルトは “仕様”! 作成関数の中に寄せるのが安定😊✅


まとめ✅🎉

  • 少ない引数ならA案もアリ🧵🙂
  • **増えたら基本はB案(オプション)**が王道🥇📦
  • オプションが多い/段階的に組み立てたい/補完で迷子を減らしたいならC案(Builder)🧱✨

そして一番大事なのはこれ👇💖 「短い」より「誤読されない」 を選ぶこと😊👀✨


ちょいメモ(2026年初頭の周辺事情)🗒️✨

  • TypeScript は 5.9.3 が “Latest” として公開されています。(GitHub)
  • TypeScript 5.8 では --module node18 が安定フラグとして触れられています。(TypeScript)
  • Node.js は v24 系が LTS として更新されています。(Node.js)