バランタインの夜
十一杯目の夜。
いつもの席に座っている。何か考えてきたわけではない。いつものように来た。いつものように重い扉を押して、いつものようにカウンターの木目に指を滑らせて、いつものように座った。
でも——今夜のバーは静かだ。
BGMの音量が違うのかもしれない。空調の音が違うのかもしれない。わからない。ただ、空気の密度が違う。10回通って身体に染みついた「いつもの空気」のほんの少し外側にいる感じ。
マスターが棚に手を伸ばした。
「17」。
ボトルに刻まれた数字が目に入った。ラベルの数字を見る癖がついたのは、いつからだろう。最初の夜は「おすすめを」としか言えなかった。6杯目の夜に自分で選んで失敗して、8杯目で「なぜこれを選んだのか」が気になって、9杯目で「書かれていないこと」に気づいた。10杯目は終売蒸留所のボトルに「もう作れない」という言葉を重ねた。
今夜は——数字が目に入ることが、もう自然になっている。
グラスに琥珀色の液体が注がれる。
「バランタイン。17年熟成でございます」
一口。
——甘い。花のような。でもその奥に何十もの味が重なっている。蜂蜜、バニラ、かすかに煙。柑橘の皮が鼻に抜けて、その後に木の実のような丸い余韻。これまでの10杯のどれとも違った。どの味もはっきりしているのに、喧嘩していない。
「……たくさんの味がする。でも一つにまとまっている」
マスターが静かに頷いた。
「40を超える蒸留所の原酒を、一つのボトルに調合しております。——一つの味を変えれば、全体のバランスが変わります」
そうだろうな、と思った。これだけの味が調和しているのなら、一つを動かせば全部が動く。でもそれが今夜の私に何を意味するのかは、まだわからなかった。
視線がカウンターの上を滑る。
取り置きボトル。
——真正面。
呼吸が止まった。
先週は一席分離れていた。先々週は三席分だった。それが今夜は、グラスを置いた目の前のラインに——麻布のボトルがある。
麻布がずれている。中の琥珀色がはっきりと見えた。バランタインと似た色。けれどバランタインのような調和した透明感はない。もっと複雑で、もっと——濁っている? いや、まだわからない。見える範囲が狭すぎる。
8杯目の夜に手を伸ばして、「まだ、早いですよ」と止められた。あの穏やかな声を覚えている。10杯目の夜に「もう少し待てる自分がいる」と思った。
今夜は——目をそらせなかった。でも手は伸ばさない。
扉が開いた。
来店——修理屋さん
若い女性が入ってきた。
ステッカーだらけのノートPCが覗くバッグを肩にかけている。目の下に疲れの影。でも姿勢は悪くない。折れていない人の背中。
カウンターを見回して、私から一席空けて座った。
「……あの、ウイスキーは詳しくないんですけど、何かおすすめはありますか?」
胸の奥が、小さく跳ねた。
10回前。最初の夜。——このカウンターに座って、同じことを言った気がする。「おすすめを」。あの時の自分と重なるような声。でも結びつかない。ただ、既視感だけが残った。
マスターが棚からもう一本ボトルを選び、ゲスト客にグラスを出した。彼女が一口飲んで、少しだけ肩の力が抜けるのが見えた。
バッグからPCを取り出す仕草が、どこか修理工場で工具箱を開ける人に似ていた。心の中で「修理屋さん」と呼ぶことにした。
「あの……見てもらってもいいですか。コードの話なんですけど」
マスターが穏やかに頷く。
修理屋さんがPCの画面をこちらにも見えるように傾けた。
「保守チームで働いてるんですけど——消費税率の改定対応を任されたんです」
画面にコードが映っている。数字が見える。0.08。同じ数字が、何行も。
「1箇所直したら、他のファイルとの不整合でテストが壊れたんです。全体を洗い出すのに2日かかりました」
修理屋さんの声は冷静だったが、指先がキーボードの端を撫でていた。
「直すのは簡単なんです。0.08 を 0.10 にすればいい。でも……なぜ15箇所に同じ数字が書いてあるのかがわからなくて」
| |
| |
言葉を失った。
この話を——前にも聞いた。
テイスティング——散弾の痕跡
最初の夜のことを思い出していた。
新人くんが「数字だらけのコードが読めない」と困っていた。マスターが「ラベルのないボトルは、中身を知る人がいなくなったとき、ただの液体になります」と言った。あの時は他人事として聞いていた。「36とか72とか謎の数字がいっぱいあるの」——自分で言った言葉。10回前の、軽い言葉。
今、修理屋さんの画面を見ている。0.08 が何行も並んでいる。あの夜の 36 や 72 と、この 0.08 は——同じ種類の問題ではないか。
マスターが修理屋さんのPCに目を向けたとき、私は言っていた。
「修理屋さんも、コードで困ってるの? うちの会社もね——消費税率の変更で15ファイルも修正が必要になったの。エンジニアがおかしいって顔してたわ」
修理屋さんが目を見開いた。「15ファイル! 私のところも同じくらいです」
一拍、置いた。
「……私も、おかしいと思った」
自分の口から出た言葉が、自分を驚かせた。「おかしい」——そう感じたのはいつだろう。15ファイル修正の報告を受けた日? このバーで10回の夜を過ごしたどこか? わからない。ただ、今夜この言葉を言えたことだけは確かだった。
マスターが——何も言わなかった。次にゲスト客への対応に移るまでの間が、いつもよりほんの一拍長い。それだけ。
修理屋さんがコードの全体を画面に出した。
「Order、Invoice、Receipt、ShoppingCart、PriceFormatter、Report。それぞれに 0.08 がハードコードされていて。0.08 を 0.10 に変えるだけなのに、15箇所あるんです」
私は修理屋さんに聞いた。
「消費税率が変わったら全部直すのって、仕方ないことだと思ってた。——だって実際変わるんだから、全部の書類を直すでしょ?」
修理屋さんが少し考えた。
「書類なら……経理はテンプレートの一箇所を直すだけですよね。税率が書いてあるマスターデータがあって、そこを変えれば全部に反映される」
息を呑んだ。
「——コードはそうなってないの?」
修理屋さんが首を振った。「なってないんです。15箇所それぞれに 0.08 って書いてあります」
マスターはこのやりとりに入ってこなかった。カウンターの向こうで静かにグラスを磨いている。
やがてマスターが修理屋さんにグラスを差し出して、穏やかに言った。
「バランタイン17年には40を超える蒸留所の原酒が使われています。ブレンダーが一つの原酒の配合比率を変えた場合——その影響は、ボトル全体の味に波及します」
修理屋さんが画面を見つめたまま頷いた。「……そうです。まさにそれです。一つの数字を変えたら、全部壊れたんです」
マスターが頷くだけで、それ以上何も言わなかった。
ブレンド——味を一箇所で管理する
マスターがバランタインのボトルを手に取った。
「40の蒸留所。それぞれが独自の味を持っています。もし配合比率を変えたいなら……」
言いかけて——止まった。いつもの夜ならすでにコードの話と重ねているはずだ。でも今夜のマスターは、ウイスキーの話からコードへの橋を渡さなかった。
修理屋さんが引き取った。「一箇所で管理すればいい——ってことですよね?」
マスターが微かに笑んだ。「ブレンダーのレシピ帳には、各原酒の比率が一覧で記されています。変更はレシピ帳の一行だけ」
修理屋さんがPCに向き直って、コードを打ち始めた。キーボードを叩く音がバーの静けさに溶ける。
「この……TaxPolicy っていうクラスを作って、税率をここに集めれば——」
| |
「Order も Invoice も、この TaxPolicy に聞けばいいんです」
| |
修理屋さんの表情が変わり始めていた。画面を見つめる目に、さっきまでの疲れとは違う光が混じっている。
マスターが水差しからバランタインに一滴加水した。
「配合比率を変えるのはブレンダーの仕事です。他の原酒が、自分で配合比率を決めることはありません」
修理屋さんが頷いた。「つまり、0.08 を 0.10 に変えるだけなら——TaxPolicy の1行だけ」
「はい。レシピ帳の一行です」
私はこのやりとりを聞きながら、言葉にならないものを抱えていた。15箇所に同じ数字がある。それがおかしいということは——10回の夜で育った何かが、教えてくれている。でもそれをどう言葉にすればいいのか、まだわからない。
「……なぜそれで問題が消えるの? クラスが増えただけじゃないの?」
修理屋さんが少し考えてから答えた。
「今まで15箇所に 0.08 が散らばっていたのは、15箇所それぞれが税率を知っている必要があったからです。でも本当は、税率を知っているべきは1箇所だけでいい。TaxPolicy に聞けばいい」
修理屋さんの声がわずかに強くなった。
「変更の理由が1箇所に閉じ込められるから、散弾銃のように飛び散らない。——Shotgun Surgery っていうそうです、この問題」
散弾銃。一発撃てば弾がばらばらに飛ぶ。一つの変更が15箇所に飛び散る。
classDiagram
class TaxPolicy {
+Num rate
+apply(amount) Int
+tax_included(amount) Int
}
class Order {
+TaxPolicy tax_policy
+calculate_tax(amount) Int
+total() Int
}
class Invoice {
+TaxPolicy tax_policy
+calculate_tax(amount) Int
+total() Int
}
class Receipt {
+TaxPolicy tax_policy
+tax_line() Str
+total() Int
}
Order --> TaxPolicy : delegates
Invoice --> TaxPolicy : delegates
Receipt --> TaxPolicy : delegates
マスターはこの会話の間、一言もコードの話をしなかった。私はそのことに、まだ気づいていなかった。
ラストオーダー——真正面のボトル
修理屋さんがPCをバッグに戻した。カウンターに手を置いて立ち上がる。
「ありがとうございました。……一箇所にまとめるだけで、全然変わるんですね」
立ち上がりかけて、私のほうを向いた。
「あの——消費税の話。15ファイル、おかしいって思えたのは、すごいことだと思います」
目を丸くした。修理屋さんは軽く頭を下げて、扉のほうへ歩いていった。重い扉が閉まる。
二人きり。
マスターがグラスを下げて、カウンターを拭き始めた。いつもの所作。磨き上げられたカウンターに反射する照明。ボトル棚の琥珀色が揺れる。
——何かが足りない。
足りないのは、言葉だった。
いつもの夜なら、このタイミングでマスターはウイスキーと絡めた一言を口にする。「ラベルのないボトルは」「器を変える勇気も」「配合の順序が」——10回の夜、マスターは必ず最後に何かを残してくれた。技術のことをコードの言葉で語るのではなく、ウイスキーの言葉で静かに差し出してくれた。
今夜は——何も言わない。
カウンターを拭き終えて、マスターが棚のボトルの位置を直している。背中が穏やか。怒っているわけでも、困っているわけでもない。ただ、言わない。
「……マスター」
声が小さくなった。
「今夜は——何も、おっしゃらないんですね」
マスターが振り向いた。いつもの穏やかな表情。
「——いかがでしたか。バランタイン」
はぐらかされた。——でも不思議と腹は立たない。10回の夜で、この人の沈黙には意味があることを知っている。
グラスに残ったバランタインをゆっくり飲み干した。40を超える蒸留所の原酒。一つの味を変えれば全体が変わる。——修理屋さんの15ファイルと同じだ。
そして——最初の夜。
「36とか72とか謎の数字がいっぱいあるの。動いてるからいいのよね」
自分で言った言葉を、10回目にして初めて外側から聞いた気がした。あの数字たちと、今夜の 0.08 は——同じ種類の問題だ。名前のない数字が散らばっていて、一つを変えたら全部が壊れる。
「……前にもね——ここで似た話を聞いた気がするの。最初の夜。数字に名前がないって話」
マスターはテイスティングノートを取り出して、何かを書いていた。私には見えない角度。
視線を落とした。
取り置きボトル。真正面。麻布がずれて、中の琥珀色がはっきり見えている。
11回通って、少しずつ近づいてきたボトル。最初の夜はカウンターの端——最も遠い位置にあった。3回目に「動いた?」と聞いた。5回目にマスターが目をやった。6回目に麻布がずれた。8回目に手を伸ばして止められた。10回目に「もう少し待てる」と思った。
今夜は——逃げようがないほど、目の前にある。
バランタインと似た琥珀色。でもバランタインのような透明感はない。もっと複雑で、もっと——濁っている? 40の蒸留所の原酒が調和した味と、この琥珀色の中にある何かは、同じものなのだろうか。
「……これ、もしかして——」
言い終えられなかった。何を言おうとしたのか、自分でもわからない。ただ、10回の夜の全部が、このボトルに集まっている気がした。
マスターはテイスティングノートから顔を上げなかった。
🥃 マスターのテイスティングノート
本日の銘柄: バランタイン 17年 お客さまの症状: 散弾銃手術(Shotgun Surgery)
ノージング(香り)── 問題の検知
一つの仕様変更——たとえば消費税率の改定——をきっかけに、何本ものファイルを開かなければならなくなったら、このアンチパターンを疑いましょう。「同じ数字」「同じロジック」が複数の場所に散在していたら、それは散弾銃の弾痕です。
パレット(味わい)── 問題の本質
15箇所に 0.08 が書いてあるということは、15箇所それぞれが「税率を知っている」と宣言しているということです。しかし税率の責任を負うべきは1箇所だけ。責務が分散しすぎると、一つの変更が散弾のように飛び散ります。これは第1回で扱った Magic Numbers の延長線上にある問題——名前のない数字が散在し、その一つを変えたとき初めて「全部が同じ理由で壊れる」ことが可視化されます。
フィニッシュ(余韻)── 解決の方針
TaxPolicy クラスに税率と計算ロジックを集約し、他のクラスは handles による委譲で税率を参照します。変更はレシピ帳の一行——TaxPolicy の rate をただ書き換えるだけ。散弾銃が一丁のライフルに変わる瞬間です。
ペアリング(相性の良いパターン)
- Single Responsibility Principle(変更理由を一つに閉じる)
- Magic Numbers(第1回)— 名前のないリテラルの散在は Shotgun Surgery の予兆
- Move Method / Move Field — 散在したロジックを集約するリファクタリング手法
「一つの味を変えれば、全体のバランスが変わります」
