第42章 直し方テンプレ:分解の手順書 🔧📌
![hex_ts_study_042[(./picture/hex_ts_study_042_refactoring_steps.png) ![hex_ts_study_042[(./picture/hex_ts_study_042_refactoring_template.png)
第42章:直し方テンプレ📌🔧「混ざったコード」をヘキサゴナルに分解する手順書 🏰🔌🧩✨
この章は、「動いてるけど、直すのが怖いコード😵💫」を 少しずつ安全に ほどいて、中心(ルール)を守れる形にしていくための “分解レシピ” だよ〜😊💖 (2026年1月時点だと、Node は 24 系が Active LTS、TS は 5.9 系が最新安定版として案内されてるよ〜🧠✨。 (Node.js))
1) この章のゴール 🎯💞
この章の最後に、あなたはこうなれる!✨
- 「この関数、責務が混ざってる😱」を 見つけられる 👀🔍
- それを Port(約束)に切り出して 🔌
- Adapter(変換・呼び出し係)へ移動して 🧩
- テストで守りながら 🧪🛡️
- “中心が外を知らない”形に直せるようになる!🏰✨
2) 分解の前に:いちばん大事な作戦 🗺️🛡️
いきなり大改造しないよ!🙅♀️💦 ヘキサゴナルのリファクタは 「一口サイズ🍰」で進めるのが勝ち✨
✅ 絶対ルール(安全装備)🧯✨
- 1ステップごとに 必ず動く状態に戻す 🔁
- 変更が小さいうちに コミット(“小刻み保存”)📌
- テストが無いなら、先に薄いテスト(動作保証)を作る 🧪
3) 今日の題材:よくある「混ぜこぜ」地獄 😵💫💥
例えばこんなの(HTTP handler が全部やってる)👇
// ❌ あるある:入力チェック・ルール・DB・レスポンスが全部ここ
export async function postTodo(req: any, res: any) {
const title = (req.body?.title ?? "").trim();
if (!title) return res.status(400).json({ message: "title required" });
// ルール(本当は中心に置きたい)
if (title.length > 50) return res.status(400).json({ message: "too long" });
// DB(外側)
const saved = await prisma.todo.create({
data: { title, completed: false },
});
// 表示(外側)
return res.status(201).json({
id: saved.id,
title: saved.title,
completed: saved.completed,
});
}
これ、動くけど…
- ルール変更が怖い😱
- DB変えたら壊れる😱
- テストしにくい😱 が揃ってるやつ!
4) 直し方テンプレ🔧📌(この順でやれば迷子になりにくい!)
ここからが本題✨ ①→②→③→④ の順でやるよ😊💕
✅ ① 混ざってる責務を発見する 👀🎨
まずは “何が混ざってる?” を見える化しよ!
🎨 色分け4分類(超使える✨)
- 🟦 入力:パース、バリデーション、トリム、型変換
- 🟥 ルール:業務判断、不変条件、状態遷移
- 🟩 外部I/O:DB/ファイル/外部API/時間/UUID
- 🟨 出力:レスポンス整形、画面表示、ステータスコード
さっきのコードで言うと👇
- 🟦
trim()と空チェック - 🟥
50文字制限 - 🟩
prisma.todo.create - 🟨
res.status().json(...)
✅ ここでのゴール
「この関数、中心(🟥)と外側(🟩🟨)が混ざってる!」って言える状態にする😊✨
✅ ② Port(約束)を “最小” で決める 🔌✂️
次にやるのはこれ👇 「中心が欲しいものだけ」をインターフェースにする✨
ポイントは 最小! 「DBの都合」じゃなくて「ユースケースの言葉」にするよ😊
②-1) まず UseCase を言語化する 🧠📝
今回なら:
- ユースケース名:
AddTodo - 入力:
title - 出力:
id/title/completed
②-2) DTO を作る(外に漏らす形)📮✨
export type AddTodoInput = { title: string };
export type AddTodoOutput = { id: string; title: string; completed: boolean };
②-3) Outbound Port(保存の約束)を最小で作る 💾🔌
export interface TodoRepositoryPort {
create(todo: { title: string; completed: boolean }): Promise<{ id: string; title: string; completed: boolean }>;
}
✅ ここが大事:Port は「中心が必要なことだけ」!
findByFooAndBarAndBaz()みたいな巨大化はあとで地獄になるよ🐘💥
✅ ③ Adapter に移動(変換と呼び出しだけ残す)🧩🏃♀️
ここで「混ぜこぜ関数」をほどいていくよ✨ 順番としては 中心→外側 がラク😊
③-1) 中心(UseCase)を作る 🏰❤️
export class AddTodoUseCase {
constructor(private readonly repo: TodoRepositoryPort) {}
async execute(input: AddTodoInput): Promise<AddTodoOutput> {
const title = input.title.trim();
// 🟥 ルールは中心!
if (!title) throw new Error("title required");
if (title.length > 50) throw new Error("too long");
return await this.repo.create({ title, completed: false });
}
}
③-2) Outbound Adapter(DB実装)を作る 🧩💾
export class PrismaTodoRepositoryAdapter implements TodoRepositoryPort {
async create(todo: { title: string; completed: boolean }) {
const saved = await prisma.todo.create({ data: todo });
return { id: saved.id, title: saved.title, completed: saved.completed };
}
}
③-3) Inbound Adapter(HTTP handler)を “薄く” する 🌐🧩
export async function postTodo(req: any, res: any) {
try {
const usecase = new AddTodoUseCase(new PrismaTodoRepositoryAdapter());
// 🟦 入力はここで(中心に文字列処理を持ち込まない意識!)
const input = { title: String(req.body?.title ?? "") };
const out = await usecase.execute(input);
// 🟨 出力もここで
return res.status(201).json(out);
} catch (e: any) {
return res.status(400).json({ message: e.message ?? "bad request" });
}
}
✅ Adapter が薄いかチェック(超重要🥗⚠️)
-
Adapter に 業務ルール が居たらアウト🙅♀️
-
Adapter の仕事はだいたい
- 変換🔁
- 呼び出し📞
- 例外を外のエラーへ変換🎭 だけでOK!
✅ ④ テストで守る(分解の成功を固定する)🧪🛡️
分解のゴールは「綺麗」じゃなくて 壊れない だよ😊💕
④-1) InMemory を作って UseCase を単体テストする 🧠🧪
import { describe, it, expect } from "vitest";
class InMemoryRepo implements TodoRepositoryPort {
private seq = 1;
async create(todo: { title: string; completed: boolean }) {
return { id: String(this.seq++), title: todo.title, completed: todo.completed };
}
}
describe("AddTodoUseCase", () => {
it("タイトルが空ならエラー", async () => {
const uc = new AddTodoUseCase(new InMemoryRepo());
await expect(() => uc.execute({ title: " " })).rejects.toThrow("title required");
});
it("正常に追加できる", async () => {
const uc = new AddTodoUseCase(new InMemoryRepo());
const out = await uc.execute({ title: "buy milk" });
expect(out.title).toBe("buy milk");
expect(out.completed).toBe(false);
});
});
✅ これがヘキサゴナルの快感ポイント💖
- DBなしで速い🧠⚡
- ルールが文章みたいに読める📖✨
- 外側を変えても中心のテストは守られる🛡️
5) “分解できてる?”セルフ診断チェックリスト ✅🔍✨
🔌 Port 最小化チェック
- Port のメソッド名が ユースケースの言葉になってる?🗣️
- DB都合の引数(
transaction,where,include)が混ざってない?😵 - “便利だから”で何でも入れてない?🐘💥
🧩 Adapter 薄さチェック
- Adapter に if/for が増えすぎてない?(増えたら🟥が混入してるサイン)🚨
- 変換・呼び出し・例外変換以外をしてない?🥗
- 中心の型(domain)を汚してない?🛡️
6) AI(Copilot/Codex)を使うと速いところ 🤖⚡(でも任せすぎ注意⚠️)
AIは「手作業がだるいところ」を任せると最強😊✨ (Portの向きとか “設計の芯” は人間が握ろうね🛡️)
🤖 コピペで使えるプロンプト集📝
①責務の棚卸し(色分け)
この関数を「入力」「業務ルール」「外部I/O」「出力」に分類して箇条書きにして。
業務ルールっぽい行を最優先で抽出して。
②Port案を最小で出す
このユースケース(AddTodo)に必要なRepositoryのインターフェースを「最小」で提案して。
DB都合の引数は入れないで、ユースケース視点の名前にして。
③Adapterが太ってないかレビュー
このAdapterのコードを見て、「業務ルールが混ざっていないか」「変換と呼び出しだけになっているか」観点で指摘して。
もし混ざっていたら、中心へ移動する候補を列挙して。
7) ちょい最新トピック(2026年1月時点)🧠✨
- TypeScript は 5.9 のリリースノートが公開されていて、5.9.3 などの 5.9 系が配布されてるよ📦✨ (typescriptlang.org)
- Node は 24 系が Active LTS(25 系は Current)という整理になってるよ🟢 (Node.js)
- ESLint は v10 の RC が出ていて、設定まわり(flat config 移行など)の変更が進んでるよ⚠️ (eslint.org)
- VS Code は “Workspace Trust” を前提に「信頼できない作業フォルダでは意図しないコード実行を避ける」仕組みがあるよ🔒(怪しいリポジトリを開く時は特に意識!) (Visual Studio Code)
- npm は
npm auditで依存関係の脆弱性チェックができるよ🧯 (docs.npmjs.com)
8) ミニ演習🎀📝(この章の手順を体に入れる!)
次のどれか1つを選んでやってみて😊✨ (選ぶなら、まずは「1つのAPI/1つのユースケース」だけ🍰)
- ✅ 課題A:既存の “混ぜこぜ handler” を、①〜④のテンプレ通りに分解
- ✅ 課題B:Outbound Port を増やしてみる(例:UUID/Clock を Port 化⏰🔌)
- ✅ 課題C:Adapter をわざと太らせてから、テンプレで救出してみる(比較で理解が爆上がり📈✨)
まとめ🎁💖(第42章の合言葉)
- ①混ざりを見つける👀
- ②約束を最小にする🔌✂️
- ③外の都合はAdapterへ🧩
- ④テストで固定🧪🛡️
もし、今あなたが「実際に直したい混ぜこぜ関数」があるなら、そのコード貼ってくれたら、このテンプレ①〜④で“どこをどう切るか”を一緒にマーキングして分解案を作るよ😊🔧✨