往診
料理は段取りが9割だ。
10年間、それを信じてフレンチの厨房に立ってきた。仕込み、ソース、メイン調理、盛り付け——頭の中のツリー構造を辿れば、何品だろうが同時に回せる。
だから、コードでも同じことができると思っていた。
俺はバンダナを外して首にかけ、モニタの前でうなだれた。ワンルームのデスクの横にはまな板とガスコンロ。壁には付箋だらけのレシピメモ。その隣のモニタには——真っ赤なエラーメッセージ。
レシピ管理アプリ。料理人のためのツール。俺が元料理人として、転職1年目の集大成として作っている個人開発アプリだ。
さっきまで動いていた。「デミグラスソース」をサブレシピとして追加したら、材料の集計がぶっ壊れた。バターが0グラムになってる。ありえない。バターの入ってないルーなんて存在しない。
インターホンが鳴った。
こんな時間に誰だ。バンダナを巻き直して、玄関を開けた。
長身の男が立っていた。黒い鞄を片手に、無表情で俺の顔を見ている。その横に、白い上着を羽織った女性が微笑んでいた。
「……ウーバーの方ですか?」
その黒い鞄が、どう見ても出前の保温バッグに見えたのだ。高級なやつの。
白衣の女性が、穏やかに首を振った。
「大丈夫ですよ、コード診療所の往診です。私はナナコ、助手をしています」
コード診療所? 往診? 頭が追いつく前に、先生と呼ばれた男はもう靴を脱いで上がり込んでいた。迷いのない足取りでデスクの前に立ち、モニタを覗き込んでいる。
「ちょ——勝手に入られても——」
先生がスクロールを止めた。画面には @recipes という配列——俺のレシピデータが全部入ったフラットなリスト。
先生の視線が、そこから壁の付箋に移った。ビーフシチューのレシピメモ。「ルー作り→バター30g、薄力粉30g」と書いた付箋が、「ビーフシチュー」と書かれた大きな付箋の中にぶら下がっている。ツリー状に。
先生は画面と付箋を3往復見比べた。
「……骨がない。」
触診
「骨?」
先生がモニタを指差した。画面をスクロールし、 $parent_id と書かれたフィールドの前で止まる。
| |
「平ら。全部。」
ナナコさんが俺のほうを向いた。
「タケルさん、料理で言うとですね——仕込み用の材料も、ソースの材料も、メインの肉も、全部同じバットにバラバラに放り込んでいる状態です。作るたびに『この玉ねぎはシチュー用? カレー用?』って仕分けし直しているんですよ」
胸がズキッとした。料理人としてありえない。そんなことをしたら厨房が崩壊する。
「い、いや、parent_id でちゃんと紐づけてますよ! フラットにしたほうが管理しやすいかなって——」
先生はさらにスクロールした。材料を集計する関数の前で指が止まる。
| |
「……毎回、組み立て直してる。」
ナナコさんが言い換えた。
「レシピのツリー構造が 骨格 だとすると、タケルさんのコードは骨が溶けてしまっているんです。臓器——材料データ——がフラットに散乱していて、使うたびに毎回外部から骨を組み立て直している状態ですね」
「骨が溶けてる……」
「はい。しかも type フィールドで材料なのかサブレシピなのかを毎回判定していますよね。自分が何者かを、自分で知らないんです」
先生が短く宣告した。
「診断。骨格溶解型フラット化症候群。」
外科手術
先生がデスクの前に座った。HHKB——小さな黒いキーボードをバッグから取り出して、俺のラップトップに繋ぐ。
ナナコさんが壁のレシピ付箋を指した。
「タケルさん、ここに貼ってあるレシピメモを見てください。ビーフシチューの下に、ルー作りがぶら下がっていますよね。さらにその下にバターと薄力粉。 ツリー になっています」
「そりゃそうだ。段取りってそういうもんだろ? 上から順に辿れば全工程がわかるように——」
「その ツリー構造 を、コードでもそのまま表現しましょう」
先生のキーの音が始まった。乾いた、リズミカルな打鍵音。
ナナコさんが画面を追いながら解説した。
「まず、材料もサブレシピも共通で持つ 骨格 を定義します。名前を返すことと、calculate() で材料を集計できること——それだけのシンプルな約束事です」
| |
「次に、材料です。単品の材料は 葉っぱ ですね。ツリーの末端。自分の量を返すだけです」
| |
「え、これだけ? バター1個が、自分で30gって知ってるだけ?」
先生が画面から目を離さずに答えた。
「……単一責務。正しい。」
ナナコさんが続けた。
「そして、ここからが Composite の核心です。レシピ——つまり 枝 ですね。材料も、サブレシピも、同じように子供として持てるようにします」
| |
俺はモニタを見つめた。$child->calculate のところを。材料でもサブレシピでも、同じ calculate を呼んでいる。type のチェックも、ref() の分岐もない。

「待ってくれ」
声が出ていた。
「これって——俺が厨房で頭の中にやってることと、まったく同じ構造じゃないか」
ナナコさんが微笑んだ。
「そうなんです。『ビーフシチュー』の下に『ルー作り』があって、その下に『バター30g』がある。料理の段取り表の構造を、 そのまま コードにしたんですよ」
「仕込みの中に仕込みがあって、一番下に材料がある。上から順に合計すれば全材料がわかる——料理ならこう考えるのに、なんでコードだとフラットにしちまったんだ俺は」
先生が小さく頷いたように見えた。
術後経過
先生がキーボードから手を離した。画面にはテスト結果——全部緑。
「……試せ。」
俺はキーボードの前に座り直した。デミグラスソースのサブレシピを追加してみる。
| |
$stew->calculate を実行する。バター30g、薄力粉30g、トマトペースト50g、赤ワイン100ml、牛すね肉400g、玉ねぎ200g、にんじん150g——全部正しい。
さっき壊れていた集計が、1行で動いている。
「サブレシピを追加しても、既存のコードは1行も変更してないです……」
$stew->display を実行する。
| |
壁に貼ってあるレシピメモの付箋と——まったく同じ形だ。
俺は冷蔵庫に向かった。
「先生、これ食ってくれませんか」
自家製プリンを出した。余った卵と砂糖で仕込んだやつだ。料理人の習性で、冷蔵庫には常に何か仕込んである。
先生がプリンを受け取った。一口。動きが止まった。目が微かに見開かれるのを、俺は見逃さなかった。そしてじっと俺の顔を見る。
その目は——厨房で料理長がコース料理の最終チェックをするときの目に似ていた。 認められた のか。料理人としての腕を。転職しても、この手が作るものに価値があるって——
「先生は甘いものが好きなだけですよ」
ナナコさんが落ち着いた声で言った。先生は何も言わず、もう一口食べている。
……そうか。
先生が帰り支度を始めた。鞄を持ち上げ、玄関に向かう。振り返らず、一言。
「感謝は、このコードに。」
「先生——」
俺はまだ胸の奥が熱かった。
「俺、厨房で10年やってきたことが、コードで再現できるって、今日初めてわかりました。テスト書きます。誓って」
先生が小さく頷いた。少なくとも、そう見えた。
ナナコさんがドアの前で振り返った。
「タケルさん、料理の段取りをコードに活かせるエンジニアって、きっとすごく強いですよ。いつでもご連絡くださいね」
ドアが閉まった。
俺はしばらくその場に立っていた。それからモニタに向き直り、 $stew->display をもう一度実行した。
画面に表示されたレシピツリーは、俺が厨房で頭の中に描いていた段取り表と、まったく同じ形をしていた。
処方箋まとめ
| 症状 | 適用すべき | 経過観察 |
|---|---|---|
| ツリー構造をフラットな配列で管理し、parent_idで再構築している | ✓ | |
葉ノード(末端要素)と複合ノード(コンテナ)を type や ref() で毎回判定している | ✓ | |
| 階層が深くなるたびに処理ロジックが壊れる・修正が必要になる | ✓ | |
| 部分と全体を統一的に操作したい(再帰的な集計・表示) | ✓ | |
| 階層が固定で2段以上にならないことが保証されている | ✓ | |
| ツリー構造の操作がなく、単なる一覧表示のみ | ✓ |
治療のステップ
- 共通骨格の定義 — すべてのノード(材料もレシピも)が共通で持つインターフェース
RecipeComponentを定義する。calculate()とdisplay()を約束事として宣言 - 葉(Leaf)の実装 — 末端要素
Ingredientを実装。calculate()は自分自身の情報を返すだけ - 枝(Composite)の実装 — コンテナ要素
Recipeを実装。子要素を持ち、calculate()は子に再帰的に委譲して集計 - 型チェックの排除 —
typeフィールドやref()による分岐を撤廃。統一インターフェースによるポリモーフィズムで呼び出し - ツリー構造の構築 —
add()で葉も枝も同じように追加。構造変更が既存コードに影響しないことを確認
助手より
料理の段取りとCompositeパターンの構造は、本当にそっくりですね。「仕込みの中に仕込みがあって、一番下に材料がある」——タケルさんが10年間、頭の中で組み立ててきたツリーは、そのままコードの設計として通用するものでした。
先生はおそらくこう言うでしょう——「構造。正しい」。短いですけど、それが最大の賛辞だと思ってください。
——ナナコ
