第7章:高凝集① “責務で切る”(関数・ファイルの基本)✂️🎯
この章でできるようになること💪✨
- 「この関数、やってること多すぎない…?😵💫」を責務(せきむ)で分解できる
- 分けた後に、**読める順番(ストーリー)**で並べ直せる📖
- “同じ理由で変わるもの”を**近く(同じ関数/同じファイル)**に集められる🏠
- VS Code の Extract Function / Rename を使って安全に分割できる🛠️
- AI(Copilot/Codex等)に「分割案」を出させつつ、最終判断は自分でできる🤖✅
ちなみに、本日時点のTypeScriptは 5.9系が現行の安定版として扱われています(GitHub Releases上の “Stable” 表記) (GitHub) (TypeScript 5.9 の公式リリースノートも公開・更新されています) (TypeScript)
7-1. そもそも「責務」ってなに?🧠💡
責務 = “このコードが守るべき役割(担当)” だよ🎀 たとえば同じ「注文する」でも、役割は別々に存在しがち👇
- 入力を整える(trimするとか)🧹
- 入力を検証する(空欄NGとか)🧪
- ルールで判断する(割引OK?など)⚖️
- 実行する(API呼ぶ)🌐
- 結果を整形する(表示用の形にする)🎁
- 失敗時の扱い(リトライ?メッセージ?)🧯
💥これが1つの関数に全部入ると、「どれかを変えたい」だけなのに全部に影響が出やすい… だから 責務で切る = 高凝集の第一歩 になるよ✂️✨
7-2. “良い分け方”のコツは「読み順=ストーリー」📖✨
分割って「細切れにすること」が目的じゃないよ🙅♀️ 目的は 読みやすくすること&変更しやすくすること🎯
おすすめはこれ👇
✅ ストーリー並びテンプレ(超よく効く)💯
- 準備(入力を整える)🧹
- 検証(ダメなら早期return)🧪
- 判断(ルール・分岐)⚖️
- 実行(外部I/Oや計算の本体)🚀
- 整形(返す形にする)🎁
これにすると、上から読んで「何してるか」がスルスル入ってくるよ📖💕
7-3. 分割の手順(迷ったらこの順でOK)🗺️✨

Step0:まず“責務ラベル”を貼る🏷️🎨
長い関数を見たら、コメントでいいからラベル付けしてみて👇 (あとで消す前提でOK!)
// 準備// 検証// 判断// 実行// 整形
Step1:境界線を引く✂️
ラベルごとに「ここからここまで同じ責務」って線を引くイメージ✍️
Step2:Extract Function で切り出す🛠️
VS Code は TypeScript のリファクタをサポートしてて、Extract Method / Extract Variable みたいな操作ができるよ (Visual Studio Code)
- Windowsだとだいたい
Ctrl + .でリファクタ候補が出る✨ - まずは “検証” を切り出すのが安全(副作用少なめ)🧪
Step3:関数名は“責務を1文で言う”📛
良い名前は「何をするか」が文章っぽくなるやつ💡
- ✅
validateOrderInput() - ✅
buildOrderRequest() - ✅
mapOrderResponseToViewModel() - ❌
doStuff()🙅♀️ - ❌
process()🙅♀️(何を?ってなる)
Step4:ファイル分割は“同じ理由で変わるもの同士”🏠
- 検証が増える →
validation側が変わる - APIの形が変わる →
api/client側が変わる - 表示用の形が変わる →
mapper側が変わる
この「変わり方」が違うなら、同じファイルに閉じ込めないほうが吉🎯
7-4. ハンズオン🛠️:長い関数を「準備/判断/実行/整形」で分割しよう🧹✨
お題:注文確定の処理(ごちゃ混ぜ版)😱
Before(わざと混ぜてる)
type OrderInput = {
userId: string;
items: Array<{ sku: string; qty: number }>;
coupon?: string;
};
type OrderResult = { ok: true; orderId: string } | { ok: false; reason: string };
export async function submitOrder(input: OrderInput): Promise<OrderResult> {
// 準備
const userId = input.userId.trim();
const items = input.items.map(x => ({ sku: x.sku.trim(), qty: x.qty }));
// 検証
if (!userId) return { ok: false, reason: "userId is empty" };
if (items.length === 0) return { ok: false, reason: "items is empty" };
if (items.some(x => !x.sku || x.qty <= 0)) return { ok: false, reason: "invalid items" };
// 判断(ルール)
const coupon = input.coupon?.trim();
const hasCoupon = !!coupon;
const payload = {
userId,
items,
discount: hasCoupon ? 10 : 0,
coupon: hasCoupon ? coupon : undefined,
};
// 実行(外部I/O想定)
try {
const res = await fakePost("/orders", payload);
if (typeof res.orderId !== "string") return { ok: false, reason: "bad response" };
// 整形
return { ok: true, orderId: res.orderId };
} catch (e) {
return { ok: false, reason: "network error" };
}
}
// ダミー
async function fakePost(_path: string, _body: unknown): Promise<any> {
return { orderId: "ORD-123" };
}
目標🎯:「上から読むだけで理解できる」形にする📖💕
ポイントはこれ👇
- 検証は早期return(失敗の出口を上に寄せる)🧪🚪
- “判断” は 名前の付いた小さな関数へ⚖️
- “実行” は 1か所へ🌐
After(責務で切った例)
type OrderInput = {
userId: string;
items: Array<{ sku: string; qty: number }>;
coupon?: string;
};
type NormalizedOrderInput = {
userId: string;
items: Array<{ sku: string; qty: number }>;
coupon?: string;
};
type OrderPayload = {
userId: string;
items: Array<{ sku: string; qty: number }>;
discount: number;
coupon?: string;
};
type OrderResult = { ok: true; orderId: string } | { ok: false; reason: string };
export async function submitOrder(input: OrderInput): Promise<OrderResult> {
const normalized = normalizeInput(input);
const error = validateInput(normalized);
if (error) return { ok: false, reason: error };
const payload = buildPayload(normalized);
const res = await postOrder(payload);
return mapResponseToResult(res);
}
// 1) 準備🧹
function normalizeInput(input: OrderInput): NormalizedOrderInput {
return {
userId: input.userId.trim(),
items: input.items.map(x => ({ sku: x.sku.trim(), qty: x.qty })),
coupon: input.coupon?.trim(),
};
}
// 2) 検証🧪(失敗理由は文字列で返すだけにしておくと扱いやすい)
function validateInput(input: NormalizedOrderInput): string | null {
if (!input.userId) return "userId is empty";
if (input.items.length === 0) return "items is empty";
if (input.items.some(x => !x.sku || x.qty <= 0)) return "invalid items";
return null;
}
// 3) 判断⚖️
function buildPayload(input: NormalizedOrderInput): OrderPayload {
const hasCoupon = !!input.coupon;
return {
userId: input.userId,
items: input.items,
discount: hasCoupon ? 10 : 0,
coupon: hasCoupon ? input.coupon : undefined,
};
}
// 4) 実行🚀(外部I/Oは“ここ”って決めると見通しが良い)
async function postOrder(payload: OrderPayload): Promise<unknown> {
try {
return await fakePost("/orders", payload);
} catch {
return { error: "network error" };
}
}
// 5) 整形🎁
function mapResponseToResult(res: any): OrderResult {
if (res?.error === "network error") return { ok: false, reason: "network error" };
if (typeof res?.orderId !== "string") return { ok: false, reason: "bad response" };
return { ok: true, orderId: res.orderId };
}
// ダミー
async function fakePost(_path: string, _body: unknown): Promise<any> {
return { orderId: "ORD-123" };
}
いい感じポイント🌸
submitOrder()が “物語” になった📖✨- “何をしてるか” が関数名でわかる📛
- 変更が起きても、直す場所が絞れる🎯
7-5. ここまでできたら「ファイル分割」も一歩だけ👣📁
全部いきなり分割しなくてOKだよ〜☺️ まずは 塊ごとに分けるだけで十分強い💪
例👇(イメージ)
submitOrder.ts(ストーリーだけ置く)📖orderNormalize.ts(準備)🧹orderValidation.ts(検証)🧪orderPayload.ts(判断/組み立て)⚖️orderApi.ts(実行)🌐orderMapper.ts(整形)🎁
💡さらに「外から触っていい関数」だけ export して、内部は隠すと、あとで崩れにくいよ🔒✨
7-6. AI活用🤖✨:分割“案”はAI、決定はあなた✅

AIは分割案を量産するのが得意だよ💡 でも「その分け方、責務が混ざってない?依存増えない?」は人間チェックが強い🔥
GitHub Copilot には、チャットの指示で複数ファイルをまとめて編集する “Copilot Edits” もあるよ(VS Code等で利用) (GitHub Docs) → ファイル分割に入るタイミングで相性よし🧩✨
この章のAIプロンプト🤖(ロードマップ準拠)
- 「この関数を責務で分割するとしたら、分割案を3つ出して(メリデメ付き)」
💡おすすめの追加指示(1行足すだけで精度UP)
- 「準備/検証/判断/実行/整形 の形で並べたい」
- 「関数名案も出して」
- 「最終的に submitOrder が上から読める形にして」
7-7. 仕上げチェックリスト✅✨(ここ超大事!)
分割後にこれだけ確認してね🧠💕
-
submitOrder()を上から読むだけで流れがわかる?📖 - 1つの関数が 2つ以上の変更理由 を抱えてない?🧨
- “検証” が他の処理(I/Oや整形)と混ざってない?🧪
- 関数名が「何をするか」を言えてる?📛
- 分割したのに、呼び出し側が逆に読みにくくなってない?(細かすぎ注意)⚠️
7-8. よくあるミス集😇💦
❌ ミス1:分割したけど、名前が弱い
handle() process() doIt() …みたいなのが増えると迷子になるよ🧭💦
👉 責務を1文で言う名前にしよ📛✨
❌ ミス2:関数を作りすぎて、逆に追いにくい
“意味が薄い1行関数”が10個…は読みにくいこともある😵 👉 「まとめたほうがストーリーが読みやすい」ならまとめてOK🙆♀️
❌ ミス3:分割したのに順番がバラバラ
準備→実行→検証→整形…みたいに並ぶと、読み手の脳が転ぶ🤸♀️💥 👉 テンプレ順(準備/検証/判断/実行/整形)へ📖✨
章末ミニ課題🎒✨(10〜20分でOK)
-
自分のコードから「ちょい長い関数」を1つ選ぶ🔎
-
責務ラベルを貼る🏷️
-
Extract Function で 3〜5個に分ける✂️
-
“ストーリー順”に並べる📖
-
最後にAIへ:
- 「責務混在してるところある?危険点を3つ」って聞く🤖✅