前回の夜が、どこか頭に残っていた。だから一週間ぶりに、あの路地裏の扉をもう一度押した。
「お戻りでしたか」
マスターが、少しだけ柔らかい声でそう言った。覚えていてくれたらしい。同じカウンター、同じ壁一面のボトル。先週と変わらない光景に、少しだけ安心する。
「今夜も、おすすめを」
まだ自分で選べない。それは前回と同じだった。
来店——俺のやり方
カウンターに座って棚を眺めていると、扉が開いた。
今度は若者じゃなかった。ジャケットの袖を捲り上げた中年の男性が、しっかりした足取りで入ってくる。カウンターの椅子にどっかりと座り、棚を一瞥した。
「ビールでいいです。——あ、ウイスキーの店ですか。じゃあハイボールで」
「かしこまりました」
マスターがハイボールを作り始めた。グラスに氷を入れ、ウイスキーを注ぎ、ソーダを静かに足す。男性はハイボールを一口飲んで、不機嫌そうに口を開いた。
「あのね、今日コードレビューで後輩に指摘されたんですよ。15年やってきて、初めてですよ、こんなこと言われたの」
15年。私の会社のエンジニアの誰よりもキャリアが長い。ベテランさん——と、すぐにそう思った。堂々とした佇まいに納得がいく。マスターは黙って聞いている。
「俺のクラスのメソッドが、別のクラスのデータを使いすぎてるって。だから何だっていうんですか。使えるデータはまとめて使ったほうが効率いいでしょ」
ベテランさんがスマートフォンを取り出し、画面をカウンターに置いた。
| |
「ReportGeneratorっていうクラスにgenerate_order_summaryってメソッドを書いて、注文データからレポートを作ってるだけです。$orderから必要な情報を取ってきて、整形して返す。何がおかしいんですか」
私はコードの中身はわからなかったけれど、$order->という文字が何度も並んでいるのは見て取れた。
ふと、自分の会社のことが頭に浮かんだ。先週のことがあったからか、「クラス」という言葉だけは覚えていた。
「うちのOrderクラス、顧客情報も在庫情報も全部持ってて。便利よね」
なぜそんなことを口にしたのか、自分でもわからない。
マスターが水差しを手に取り、私のグラスにほんの一滴を落とした。静かな所作だった。何の意味があるのかは、わからなかった。
テイスティング——いろんな顔を持つお酒
マスターが私の前にグラスを置いた。先週の琥珀色とは違う、少し明るい金色だった。
「ジョニーウォーカー グリーンラベルでございます。スコットランドの4つの蒸留所——それぞれ個性の異なる原酒を、一つのボトルにブレンドしたウイスキーです」
一口含んだ。先週のグレンファークラスほど強烈ではないけれど、飲むたびに味が変わる。甘み、スモーキーさ、果物のような香り。不思議なお酒だった。
「4つも蒸留所を使うの? 一つの蒸留所だけじゃだめなの?」
「だめではありません。ただ——」マスターは少し間を置いた。「他の蒸留所の原酒に手を伸ばしすぎると、自分の味がわからなくなることがあります」
マスターの視線がベテランさんのほうに移った。
「お客さま、先ほどのコードを拝見してもよろしいですか」
ベテランさんが画面を差し出す。マスターはしばらく眺めてから、静かに言った。
「generate_order_summaryというメソッドですね。ReportGeneratorクラスに置いてあります。——しかし、このメソッドが触っているデータは、すべて$orderのものです」
「だから何です? データが向こうにあるんだから、取りに行くのは当然でしょう」
ベテランさんの声には、苛立ちが混じっていた。
マスターがグラスを一つ持ち上げて、ベテランさんの前に置いた。
「お客さまのグラスがここにあるのに、隣のお客さまのグラスに手を伸ばして飲もうとしたら——どうなるでしょうか」
「……喧嘩になりますね」
「この状態を、Feature Envyと呼びます。メソッドが、自分のクラスよりも他のクラスのデータを羨ましがっている状態です」
ベテランさんの眉がぴくりと動いた。が、すぐに腕を組み直した。
「比喩としてはわかりますけど、コードの話でしょう。動いてるものを直す理由がわからない」
私はつい横から口を挟んでいた。
「ねえ、でも——全部自分のところにまとめたほうが便利じゃない? うちの会社でも、一つの部署で全部やれたら楽なのにって思うもの」
ベテランさんが私のほうを向いた。「そうでしょう? この方のほうが話がわかる」
マスターが一拍だけ黙った。それから、穏やかに言った。
「便利と依存は、似た味がしますね」
その一言が、なぜか引っかかった。ベテランさんも口を閉じた。
マスターがベテランさんの画面にもう一度目を落とした。
「お客さま。もしOrderクラスのtotal_priceが、明日subtotalに変わったら——このメソッドはどうなりますか」
「……壊れますね。$order->total_priceを呼んでるから」
「ええ。自分のクラスなのに、他人の変更で壊れる。隣のグラスに手を伸ばしていると、隣の方が席を立つだけで、こちらのグラスも倒れます」
ベテランさんの表情が変わった。マスターは続けた。
「テストを書くときも同じです。このメソッドを検証するには、Orderのモックを作り、5つのアクセサをすべてスタブしなければならない。自分のメソッドなのに、他人の道具がなければテストもできません」
「……テストは確かに面倒なんですよね」
ベテランさんの声が静かになった。マスターの比喩ではなく、自分自身の実感から出た言葉だった。
ベテランさんがハイボールに目を落とした。
「要するに、このメソッドは——居場所を間違えてる、ってことですか」
「ええ。このメソッドは、Orderクラスにいたがっているとも言えます」
ブレンド——自分のグラスを飲む
「解決の第一歩は、メソッドを適切なクラスに移動することです。Move Methodと呼ばれるリファクタリングです」
マスターはカウンターの下からテイスティングノートを取り出し、何かを書き留めてから、ベテランさんに向き直った。
「先ほどのメソッドの中身を、Orderクラスに移動します」
| |
ベテランさんが身を乗り出した。「え、ReportGeneratorがなくなるんですか?」
「なくなるのではありません。メソッドが本来いるべき場所に帰るだけです。加水と同じです——水は外から注ぐものですが、ウイスキーと混ざった瞬間に、その一杯の一部になる。データと振る舞いが同じクラスに収まるとは、そういうことです」
私はベテランさんの画面を覗き込んだ。さっきのコードと何が違うのか——と思って、気づいた。
「あ。$self->ばっかりになってる。さっきは$order->ばっかりだったのに」
ベテランさんが驚いた顔で私を見た。「お姉さん、エンジニアじゃないですよね?」
「違うけど、並んでる文字が変わったのはわかるわよ」
マスターが小さくうなずいた。
「まさにそこです。$order-> の羅列が $self-> に変わった」
マスターはベテランさんに向き直った。
「先ほどのtotal_priceの話を覚えていますか。subtotalに変えたくなっても——」
「同じクラスにあるから、一緒に直せる」
ベテランさんが引き取った。マスターがうなずく。
「テストも、Order->new(customer_name => 'テスト', ...)->summary と呼ぶだけで済みます」
「モックが要らなくなるのか……」
ベテランさんの声に、ようやく納得の色が混じった。
「注文の要約はOrderが作る。データを持ってるやつが、いちばんよく知ってるわけだ」
マスターが静かにうなずいた。
「自分のグラスの中身は、自分が一番よく知っています」
だが、ベテランさんはすぐに別の疑問を口にした。
「でも、ReportGeneratorからOrderの情報を使いたいときはどうするんです。全部移動するわけにもいかないでしょう」
「いい質問です。Mooには委譲という仕組みがあります」
| |
「handles => ['summary']と書くことで、ReportGeneratorで$self->summaryと呼べば、内部的に$self->order->summaryが実行されます」
ベテランさんがじっとコードを見ている。
「$order->customer_nameとか$order->tax_amountとか、一個一個手を突っ込む必要がなくなる……」
「ええ。他人のグラスに手を突っ込むのではなく、『この一杯をください』と注文する。それが、バーの作法です」
ベテランさんは長い沈黙の後、ハイボールを一口飲んだ。
「……後輩が正しかったってことか」
「後輩の方は、よく嗅覚が働いています」
ラストオーダー——便利の正体
ベテランさんが立ち上がった。来たときの不機嫌さは消えて、何かが腑に落ちた顔をしていた。
「月曜に後輩に謝ります。——で、一緒にリファクタリングしますよ」
カウンターに代金を置いて、ベテランさんはふと私のほうを振り返った。
「お姉さん。あの『並んでる文字が変わった』って気づき、けっこう鋭いですよ」
「えっ、そう? ……ありがとう」
扉が閉まって、二人きりになった。
ジョニーウォーカー グリーンを傾けながら、さっきの話を反芻していた。
「マスター。便利と依存の話、ちょっとだけわかるかも」
マスターが静かにこちらを向いた。
「うちの会社でも、なんでも営業部に頼んじゃうの。顧客情報の確認も、見積りの計算も、在庫の問い合わせも。楽だけど——営業部がパンクしかけてるのよね」
自分で言ってから、なぜコードの話で会社のことを思い出したのか、不思議だった。
マスターは水差しを手に取り、私のグラスにほんの少し加水した。
「——いかがでしょうか」
一口含んだ。さっきと味が違った。同じお酒なのに、甘みの奥にスモーキーな輪郭が浮かび上がっている。
「……おいしい。さっきより、味がはっきりする」
「適切な距離が、味を開かせることもございます」
カウンターの端に目をやった。先週もあった、麻布をかぶったボトル。
「あのボトル、先週もありましたよね」
「ええ。まだ、お取り置きのままです」
穏やかな微笑みだけで、それ以上は何も言わない。
マスターがテイスティングノートに何かを書き留めている。私はもう訊かなかった。きっと、お客さまの好みの記録だろう。
店を出ると、夜風が少しだけ肌寒かった。前回より、あのお酒の味がわかった気がする——ほんの少しだけ。
🥃 マスターのテイスティングノート
本日の銘柄: ジョニーウォーカー グリーンラベル
お客さまの症状: フィーチャーエンビー(Feature Envy)
ノージング(香り)── 問題の検知
メソッドの中で$selfよりも$other->のアクセサ呼び出しが多いなら、このアンチパターンを疑いましょう。自分のクラスのデータをほとんど使わずに、他のクラスのデータばかり取りに行くメソッド——それは、隣の客のグラスに手を伸ばしている状態です。
パレット(味わい)── 問題の本質
Feature Envyの根本は「データと振る舞いの分離」です。データを持つクラスと、そのデータを使うメソッドが別々のクラスにある。この分離が、変更の連鎖(他クラスの属性変更で壊れる)、テストの困難(モック地獄)、責任のねじれ(誰がこの処理を担うべきか曖昧)を引き起こします。
フィニッシュ(余韻)── 解決の方針
Move Methodで、メソッドをデータのあるクラスに移動するのが第一歩。$order->customer_nameの羅列が$self->customer_nameに変わり、変更が閉じ、テストが直接書けるようになります。呼び出し側が必要なら、Mooのhandlesで委譲を使うことで、他クラスの内部構造に手を突っ込まずに機能を借りられます。
ペアリング(相性の良いパターン)
- Move Method —— Feature Envy 解消の定番リファクタリング
- Moo の
handles(委譲) —— 他クラスの機能を安全に借りる仕組み - Single Responsibility Principle —— データと振る舞いを同じ責任単位に凝集させる
「便利と依存は、似た味がしますね」
