六杯目を、自分で選ぼうと思った。
いつもの席に座って、壁一面のボトルを見上げる。グレンファークラス、ジョニーウォーカー、ボウモア、グレンドロナック、白州——五回分の記憶が、ラベルの向こうにある。どれもマスターが選んでくれたものだった。
今夜は違う。理由はない。ただ、六回も通えば自分の好みくらいわかっていいはずだ。
棚の中段に、目を引くボトルがあった。ラベルには黒地に金の文字。どっしりとした暗い緑のガラス瓶。なんとなく力強そうだ。
「あれを」
指差した。マスターが私の指先を追って棚を見上げ——それからこちらを見た。一拍の間があった。いつもならすぐに返事をくれるのに。
「かしこまりました」
グラスに注がれた液体は、琥珀というより黒蜜に近い濃さだった。粘り気がある。グラスの壁をゆっくり伝い落ちる。
「アードベッグ ウーガダールでございます。アルコール度数は54.2%。カスクストレングス——樽から出したままの度数でございます」
鼻を近づけた。煙の向こうに何かがある。甘い? でもまず煙が来る。ピート。前にマスターが教えてくれた言葉だ。
一口、含んだ。
喉が、焼けた。
煙と一緒にアルコールの刺激が鼻を突き抜けて、甘さなんてどこかに吹き飛んでしまった。咳き込んで、思わずグラスをカウンターに置いた。
「きつい……!」
目に涙が浮かんでいる。恥ずかしい。
マスターは怒りもせず、呆れもせず、穏やかに言った。
「カスクストレングスは、加工しない原酒の力強さをそのまま味わえるウイスキーです。——しかし、そのままでは飲む方を選びます」
マスターがスポイトを取り出して、グラスに一滴だけ水を垂らした。
「少々お待ちください」
「え、一滴だけ……?」
マスターは微笑んだだけで答えなかった。
しばらく待って、もう一度口をつける。
——あれ。
さっきのアルコールの暴力が嘘のように引いて、その奥にダークチョコレートの苦みと、レーズンの甘さが顔を出した。煙はある。でもその煙が、今度は味を覆い隠すのではなく、味を引き立てるフィルターのように機能している。
「全然違う。同じお酒なの、これ?」
「同じ原酒でございます。加水というほんの少しの加工が、隠れていた味を引き出します」
同じ液体。同じ原酒。なのに一滴の水で、まるで別の飲み物になった。私は自分で選んで、いきなり原酒をストレートで飲もうとしたのだ。選ぶことと、正しく選ぶことは違う。
カウンターの端に目がいった。取り置きボトル。あの麻布をかぶったボトルが——今夜はいつもと違った。麻布がずれている。ほんの少し下にずれて、瓶の下のほうが見えていた。照明を受けて、琥珀色の液体が光っている。
「……綺麗な色」
マスターの手がカウンターの上で止まった。それからいつもの調子で答えた。
「まだ仕上がっていません」
穏やかだけど、きっぱりしていた。「仕上がっていない」。何を仕上げているのだろう。でもこのボトルが少しずつ私に近づいてきていることと、今夜初めて中身が見えたことは——何かの変化なのだと思う。
来店——荒い原酒と自由の代償
扉が軽快に開いた。
大きなトートバッグを肩にかけた女性。ノートPCが覗いている。パーカーにスニーカー。今夜のバーの客層——といっても私とマスターしかいないけれど——からすると、ずいぶんラフな格好だ。でもそれがよく似合っている。肩の力が抜けていて、自分の足で歩いている感じがする。
「こんばんは。ここがコードの相談できるバーって聞いたんだけど。面白そうじゃない」
フリーランスさん、と心の中で呼んだ。会社に所属していない人の空気がある。自由で、軽くて、でも一人で全部背負っている強さみたいなもの。
マスターが声をかけた。
「いらっしゃいませ。今夜は何をお召し上がりになりますか」
「ハイボールで。あ、スモーキーなやつがあれば嬉しい」
注文の仕方にも迷いがない。いいな、と思った。私は六杯目でようやく自分で選んでみて、咳き込んだのに。
フリーランスさんはハイボールをぐいっと飲んで、すぐに本題に入った。
「あたし、フリーランスで15年やってるの。最近バグ報告が続いてて——コード自体は動いてるのに、データがおかしいのよね」
「具体的には、どのようなバグでしょうか」
マスターが静かに訊いた。
「メールアドレスのフィールドに電話番号が入ってたり、金額を文字列のまま足し算して桁がずれたり。全部文字列とか数値で持ってるんだけど、何が悪いのかがわかんないのよ」
文字列で持っている——。私は思わず口を挟んだ。
「あ、うちもそう。メールアドレスも電話番号も全部文字列で持ってるけど——でもメールアドレスのところに電話番号が入るって、どういうこと?」
マスターの返事までの間が、一拍だけ長かった。気のせいかもしれない。でも今夜はその「一拍」が妙に引っかかった。
マスターはフリーランスさんに向き直った。
「コードを拝見してもよろしいですか」
フリーランスさんがトートバッグからノートPCを取り出し、画面をカウンターに向けた。
| |
私にはコードの詳しいことはわからない。でも $email とか $phone という名前が見える。それぞれメールと電話のことだろう。
フリーランスさんが胸を張って言った。
「Str と Int で型もつけてるでしょ? これで十分じゃない? あたし15年このスタイルでやってきたのよ」
マスターが穏やかに訊いた。
「では、この add_customer を呼ぶとき、$email に電話番号を、$phone にメールアドレスを渡したら、どうなりますか」
フリーランスさんの手が一瞬止まった。
「……動く。エラーにはならない」
「それが、問題でございます」
テイスティング——蒸発した意味
マスターがカウンター越しにフリーランスさんのコードを見ている。指一本触れずに、目だけで読んでいる。
「$email は Str 型です。文字列であれば何でも受け入れます。電話番号も、住所も、お名前も——すべて文字列です。つまり $email という名前が意味を持っているのは、プログラマの頭の中だけで、コードはその約束を何も保証していません。——この状態を、Primitive Obsession、プリミティブ型への執着と呼びます」
私は「プリミティブ」という言葉を前にも聞いたことがある気がした。確か一回目の夜、マジックナンバーの話のときに。数字にも名前がつく問題がある、とマスターが言っていた。今度は文字列にも名前がつく問題があるらしい。
「でも、型って制約でしょ?」
私が言った。会社でもいつも考えていることだ。
「会社の規則みたいなもので、多いほど窮屈になるんじゃない? ルールは最小限にしたいのが経営者の本能よ」
フリーランスさんが勢いよく同意した。
「そう! まさにそれ。あたし15年、型に縛られたコードなんて見てると息苦しくなるのよ。Perlは自由に書けるのがいいところじゃない」
わかる。フリーランスさんの「自由にやりたい」は、私の「ルールは最小限にしたい」と同じ匂いがする。一人で全部やってきた人の、軽さと潔さ。
マスターがフリーランスさんに静かに訊いた。
「ご質問させていただいてもよろしいですか。お客さまの案件で、メールアドレスの入力を受け付ける場所はいくつありますか」
フリーランスさんが天井を見上げて指を折った。
「7箇所くらい……かな」
「その7箇所すべてで、メールアドレスのバリデーションを行っていますか」
沈黙。
「……3箇所はやってる。あとの4箇所は、まあ、入力する人がまともなアドレスを入れるでしょ、って」
「3箇所にバリデーションがあり、4箇所にはない。同じ概念を7箇所に散在させて、そのうち4箇所は『信頼に依存』しています」
フリーランスさんの眉がわずかに動いた。図星、という顔ではなかった。でも「あれ」という顔だった。自分でも薄々わかっていたけど、言語化されたのは初めて——そういう表情。
マスターが続けた。
「Primitive Obsession が問題になるのは、3つの理由がございます。第一に、意味の蒸発です。Str は『文字列である』としか言っていません。メールアドレスであること、電話番号であること——ドメインの意味がコードから蒸発しています。プログラマの記憶だけが意味を保持しており、記憶は引き継げません」
フリーランスさんが口を開きかけた。引き継ぎ——フリーランスだから、案件を離れたら次の人が保守する。
「第二に、バリデーションの散在です。同じ制約を、受け入れ口ごとに書く羽目になります。7箇所あれば7回。1箇所でも忘れれば、不正なデータが入り込みます。お客さまのバグ報告は、まさにこのパターンです」
フリーランスさんが視線を落とした。4箇所の穴。自覚はあったのだろう。
「第三に、型の嘘です。$email という変数名は『メールアドレスである』と主張しています。しかし Str 型は電話番号も受け入れます。変数名が嘘をついている。その嘘は検出されないまま本番まで到達します」
フリーランスさんがカウンターに手をついた。
「バリデーション書き忘れってだけの話でしょ。全箇所にバリデーション入れればいいじゃない」
「その方法でも修正はできます」
マスターは否定しなかった。
「——しかし、次に新しい画面を作るとき、また同じバリデーションを書くことを覚えていられますか。そしてその次の人は」
私は7箇所に同じチェックを入れる情景を想像してみた。7箇所——うちの会社にも似たような仕組みがなかったか。そうだ、経費精算。システムが3つに分かれていて、入力チェックがバラバラだって経理の子が愚痴を言っていたな。
マスターが私のグラスを軽く示した。
「さきほどカスクストレングスをお召し上がりいただきましたね」
「はい。最初きつくて死ぬかと思いました」
「加工しない原酒——つまり何の制約もかけない値——は、力強い。しかし、そのままでは味がわからない。一滴の加水というほんの少しの約束が、隠れていた味を引き出しました」
マスターがゆっくりと、でもはっきりと言った。
「制約は制限ではなく、約束です。値が守るべき約束を型として表現する——それが解決策です」
ドキッとした。「制約ではなく約束」。私の会社で「ルールは最小限に」と言ってきたけど、それは制約を減らすことであって、約束をなくすこととは違う。もし約束がないまま動いているなら——。
口には出さなかった。でもグラスの中のアードベッグを見つめながら、それだけ考えていた。
ブレンド——約束を型にする
マスターがカウンター越しにフリーランスさんに向き直った。
「解決策は、Value Object の導入です。値に名前を付け、制約を持たせ、不正な状態では生まれてこないオブジェクトにします。まず、メールアドレスを Value Object として定義しましょう」
マスターがフリーランスさんのノートPCのそばに、自分のタブレットを置いた。そこにコードが映っていた。
| |
フリーランスさんが目を丸くした。
「え、クラスを作るの? 文字列のためだけに?」
「はい。メールアドレスは『文字列』ではありません。形式の約束を持つドメインの概念です。このクラスのポイントは3つあります。第一に、is => 'ro'。一度作ったら変更できません。不変です。メールアドレスが途中で変わったら、それは別のメールアドレスです」
フリーランスさんがコードに目を戻した。
「第二に、BUILD でのバリデーション。不正な値では生まれてこない。このオブジェクトが存在している時点で、有効であることが保証されています。バリデーション忘れは構造的にゼロになります。——そして第三に、名前があります。Str ではなく EmailAddress。変数名に頼らず、型そのものが意味を伝えます」
マスターがタブレットをスクロールした。
「同様に、電話番号と年齢も Value Object にします」
| |
| |
「そして、これらの Value Object を組み合わせて、顧客を定義します」
| |
フリーランスさんが腕を組んだ。「InstanceOf ね。で、これをどう使うわけ?」
マスターがもう一つのコードを見せた。
| |
「コード量は増えてるわよね」
フリーランスさんの指摘は正確だった。
「はい。行数は増えます。——しかし、バリデーションの数は減ります」
マスターが静かに続けた。
「Before では7箇所にメールアドレスの入力があれば、7箇所にバリデーションが必要でした。After では EmailAddress->new が1箇所で検証を完結させます。7箇所のどこで作っても、同じ約束が守られます」
フリーランスさんがしばらく黙ってコードを見つめていた。キーボードの上で指が止まっている。それからゆっくりと口を開いた。
「……ああ、そういうことか」
立ち上がりかけて、座り直した。
「あたしが7箇所にバリデーション入れる代わりに、1個のクラスが全部保証してくれるのね。しかも Customer の email が InstanceOf['EmailAddress'] だから、PhoneNumber を渡そうとしたら——」
「その時点で、エラーになります」
フリーランスさんの目が変わった。さっきまで「面白いじゃない」だった表情に、理解の光が差した。
「取り違えが、存在しなくなる。忘れるとか忘れないの問題じゃなくて、構造的に不可能になる」
マスターがゆっくりうなずいた。
「おっしゃる通りです」
私はフリーランスさんの横顔を見ていた。自分で気づいたのだ。マスターに教わったのではなく、コードを見つめて、自分で答えにたどり着いた。15年の経験があるから、一度理解すれば早い。
マスターが続けた。
「3つの問題がどう変わったかを確認しましょう。第一に、意味の復活です。EmailAddress という型が『これはメールアドレスである』と宣言しています。変数名に頼らず、型が意味を保持します。プログラマの記憶が消えても、意味は残ります」
フリーランスさんが小さくうなずいた。
「第二に、バリデーションの一元化です。バリデーションは EmailAddress の BUILD に一箇所。7箇所の書き忘れリスクはゼロです。新しい画面を作る人も EmailAddress->new を使えば、自動的に検証されます。——そして第三に、型が嘘をつけないことです。先ほどの Customer で見たように、InstanceOf['EmailAddress'] は EmailAddress のインスタンスしか受け入れません。電話番号を渡すことは、構造的に不可能です」
私は前にマスターが言った言葉を思い出した。ラベルのないボトル。中身を知る人がいなくなったら、ただの液体になる。今夜の話はその続きだ。
「つまり、ラベルのないボトルにラベルを貼って、中身まで保証するってこと?」
マスターが私を見た。穏やかだけど、少し驚いたような目。
「ええ。そしてラベルが貼られていないものは、棚に並べない。——それが約束です」
ラストオーダー——疑問形の夜
フリーランスさんがトートバッグを肩にかけ直した。ノートPCをしまいながら、カウンターに目を落として言った。
「15年かあ。ずっと自由に書いてきたけど、自由ってのは約束がないことじゃなくて、約束を自分で決められることだったのかもね」
それからこちらを見て、軽く手を挙げた。
「ね、お姉さんの会社もフリーランスに発注してる? 型をちゃんと使ってる人に頼んだほうがいいわよ」
苦笑した。「あたしも型なんて要らないと思ってた側なんだけど」
フリーランスさんは軽く笑って、扉を押した。入ってきたときと同じ軽い足取りで出ていく。でもどこか、さっきより背筋が伸びていた気がする。
カウンターに私とマスターだけが残った。
手元のグラスに目を落とした。マスターが加水してくれたアードベッグ ウーガダール。最初のカスクストレングスの衝撃が嘘のように、煙の奥にチョコレートと果実味が広がっている。同じ原酒。同じ液体。なのに、一滴の水で別物になった。
「マスター、最初の一口、本当にきつかったです」
「加工しない原酒には、力も味も詰まっています」
マスターがグラスを磨きながら言った。
「けれど、適切な器とほんの少しの加工がなければ、その良さは届きません」
器。型のことだろうか。Value Object という器に入れるから、中身が生きる。入れないまま出したら——飲む人が火傷する。さっきの私のように。
「メールアドレスが文字列って、普通だと思ってました」
「ええ。多くの方がそうおっしゃいます」
返事の間。いつもと同じだった。今夜のマスターは、あの一拍の長い沈黙を最初に一度だけ見せて、あとはいつもどおりだった。それがかえって気になった。
ふと思い出した。最初にカスクストレングスを自分で選んだこと。棚を見て「あれを」と指差した。五回分の経験が、「自分で選ぶ」勇気をくれた。でも選ぶことと、正しく選ぶことは違った。
「次は——もう少し上手に選べるかな」
「選ぶこともまた、学びでございます」
帰り支度をするとき、取り置きボトルをもう一度見た。麻布がずれたまま。琥珀色の中身が光っている。
「綺麗な色」
二度目のその言葉に、マスターは答えなかった。カウンターを拭いていた。
路地裏を歩いた。フリーランスさんの言葉が頭に残っている。「全部文字列で持ってるって普通でしょ」。そう、私もそう思っていた。マスターの「約束」という言葉。約束がなくても動いてる。動いてるけど——。
「動いてる……わよね?」
口に出して、自分で驚いた。私は、疑問に思ってしまったのだ。
口に残るアードベッグの煙たさが、まだ消えなかった。
🥃 マスターのテイスティングノート
本日の銘柄: アードベッグ ウーガダール
お客さまの症状: プリミティブ型への執着(Primitive Obsession)
ノージング(香り)── 問題の検知
Str や Int だけで顧客情報を保持し、メールアドレスと電話番号の取り違えがエラーにならないコード。変数名だけが意味を持ち、型は何も約束していない——そのとき、このアンチパターンを疑いましょう。
パレット(味わい)── 問題の本質
プリミティブ型は「文字列である」「整数である」としか言っていません。ドメインの意味がコードから蒸発し、バリデーションが受け入れ口ごとに散在します。7箇所に同じチェックを書いて、8箇所目で忘れたとき——不正なデータは静かに本番に届きます。
フィニッシュ(余韻)── 解決の方針
Value Object を導入し、値に名前と制約を持たせます。EmailAddress->new が存在する限り、バリデーションは自動的に適用されます。Before ではプログラマの記憶に依存していた「約束」が、After では型として刻まれ、取り違えは構造的に不可能になります。
ペアリング(相性の良いパターン)
- Types::Standard による宣言的な型制約(
InstanceOf,StrMatch) - Introduce Parameter Object(データの群れを一つのオブジェクトにまとめる)
- Magic Numbers(第1回)との関連——数値のラベルと、文字列のラベル
「適切な器とほんの少しの加工がなければ、原酒の良さは届きません」
