Skip to main content

第26章:三角測量(2例目で一般化)📐

三角測量

🎯 今日のゴール

  • 「最初はベタでOK」→ **2つ目の例(テスト)で“ちゃんと一般化”**できるようになる💪✨
  • 2例目の選び方が分かって、TDDがスムーズに回るようになる🔁💕

📌 三角測量ってなに?(超かんたん)

TDDって、最初は「通すための超ベタ実装(仮実装)」でもOKだよね🩹 でもそのままだと “たまたま通ってるだけ” になりがち…😵‍💫

そこで 三角測量(Triangulation)

  • 1つ目の例:まず通す(ベタでもOK)
  • 2つ目の例:ベタが通らなくなるように “別の例” を追加
  • するとコードが 自然に一般化される(=ちゃんとしたロジックになる)📐

この考え方は「抽象化(一般化)は、例が2つ以上そろってからが一番安全」という超保守的なやり方として紹介されてるよ📘✨ (スタニスワフのテックノート)


✅ 2例目の“いい選び方” 3ルール 📏✨

ルール1:結論(期待値)が変わる例を選ぶ

同じ結果になる例を追加しても、ベタ実装が壊れない=一般化が起きない😿 👉「送料500円」→ 次は「送料0円」みたいに、結果が変わるのが強い💥

ルール2:変える要素は1つだけ

いきなり「境界+例外+丸め」みたいに盛ると、どこで壊れたか分からなくなる🥺 👉 2例目は “最小の差分” にするのがコツ🎯

ルール3:境界(しきい値)を狙うと当たりやすい

「◯円以上なら無料」みたいな仕様って多いよね🧾 👉 2例目を ちょうど境界に置くと、最小の追加で一般化できる💡


🧪 ハンズオン:送料無料ラインの計算(三角測量の定番)📦✨

お題:送料を決める関数

  • 小計が 3000円以上 → 送料 0円 🆓
  • 小計が 3000円未満 → 送料 500円 🚚

① まず1本目:未満なら500円(ここはベタでOK)🩹

テスト(1本目)👇

import { describe, it, expect } from "vitest";
import { calcShippingFee } from "../src/shipping";

describe("calcShippingFee", () => {
it("小計が送料無料ライン未満なら送料は500円", () => {
expect(calcShippingFee(2500)).toBe(500);
});
});

実装(仮実装)👇 「とにかく通す」だけ✨

export function calcShippingFee(subtotalYen: number): number {
return 500; // 仮実装🩹
}

ここまででOK!気持ちよくGreen🎉


② 2本目のテスト:境界をまたいで「0円」を要求する🆓💥

ここが 三角測量のキモ📐✨ “同じ500円”じゃなくて、結果が変わる例を入れるよ!

import { describe, it, expect } from "vitest";
import { calcShippingFee } from "../src/shipping";

describe("calcShippingFee", () => {
it("小計が送料無料ライン未満なら送料は500円", () => {
expect(calcShippingFee(2500)).toBe(500);
});

it("小計が送料無料ライン以上なら送料は0円", () => {
expect(calcShippingFee(3000)).toBe(0);
});
});

この時点で、さっきの仮実装は確実に落ちるよね😄(ずっと500返してるから)


③ 最小の一般化:条件分岐にする(必要な分だけ)🌿

「2つの点」がそろったから、ここで一般化するよ📐✨

const FREE_SHIPPING_THRESHOLD_YEN = 3000;
const SHIPPING_FEE_YEN = 500;

export function calcShippingFee(subtotalYen: number): number {
if (subtotalYen >= FREE_SHIPPING_THRESHOLD_YEN) return 0;
return SHIPPING_FEE_YEN;
}

はい、これで2本ともGreen✅🎉 ベタ実装→2例目で破壊→最小一般化の流れ、これが三角測量だよ🫶


🔍 仕上げの“確認テスト”(任意)🧷✨

三角測量の主役は「2例目」だけど、境界のちょい下を押さえると安心感UP💕

it("小計が境界の1円下なら送料は500円", () => {
expect(calcShippingFee(2999)).toBe(500);
});

「境界の扱い」が仕様としてハッキリするよ📘✨


🤖 AIの使いどころ(ズルじゃないよ!)😎💞

AIは “次の一手を小さくする” のがめちゃ得意!✨

✅ 使えるプロンプト例

  • 「今の実装(仮実装)が通っちゃうので、最小の2例目を3つ提案して。期待値も書いて」
  • 「2例目を追加して落ちた。最小の変更で通す実装案を出して(読みやすさ優先)」
  • 「境界値テスト、入れすぎずに安心できる本数を提案して」

※“仕様そのもの”をAIに決めさせるんじゃなくて、選択肢を増やす係にするのが安定だよ🤖✨


✅ ありがちな失敗あるある(先に潰そ)🧯💥

  • 2例目が弱い(結果が同じ) → 仮実装が壊れず、一般化が起きない😿
  • 2例目で盛りすぎ(要素を2つ以上変える) → どこを直せばいいか迷子になる🌀
  • 一般化しすぎ → 未来の仕様まで勝手に想像して複雑化…😵 → “今のテストが通る最小”だけでOK👌

✅ チェックリスト(できたら合格🎀)

  • 1本目は仮実装でも通せた
  • 2本目は 期待値が変わる例を選べた
  • 実装の変更は 最小の一般化で済ませた
  • (任意)境界の1円下/上など、仕様が明確になった

🧠 おまけ:2026年1月時点の“最新メモ”📌✨

  • Node.js は v24 が Active LTS、v22/v20 は Maintenance LTS という扱いになってるよ🟩 (Node.js)
  • TypeScript は GitHub上の最新リリースが 5.9.3(2025-10-01)になってるよ🧩 (GitHub)
  • Vitest は npm 上で 4.0.17 が最新として表示されてるよ🧪 (NPM)
  • TypeScript 6.0/7.0 の進捗も公式ブログで継続共有されてるよ(“橋渡しリリース”の話など)📝 (Microsoft for Developers)

次の第27章(明白な実装)では、「三角測量をやらずに最初から素直に書いてOKな場面」も整理して、使い分けができるようにするよ🌼✨