Featured image of post コードバーテンダー【Speculative Generality】終売ボトルの記憶〜設計図だけの未来〜

コードバーテンダー【Speculative Generality】終売ボトルの記憶〜設計図だけの未来〜

Speculative Generalityは「将来のため」の過剰な抽象化。MooでYAGNI原則に基づき不要なロール・基底クラスを除去し、シンプルな設計へ回帰する。

蒸留所のない夜

十杯目の夜。

路地裏の扉を押した。いつもの重さ。いつもの軋み。——でも、中の空気が違った。

カウンターに、見知らぬ人が座っている。マスターの正面。紙のようなものを広げて、何かを指さしながら話していた。

足が止まった。九回通って初めて、自分以外の客がマスターと話している場面に入っていく。

マスターがこちらに気づいた。軽く会釈する。

「いらっしゃいませ。——どうぞ、おかけになってください」

いつもの穏やかな声。でも一瞬の間があった。対応を切り替える、ほんの小さな呼吸。

いつもの席に座った。二席分の空間を挟んで、ゲスト客がいる。紙に描かれた箱と矢印が覗いた。——先月スマホで見ていた自社のシステム構成図と似た、箱と線の図。

声の断片が耳に入る。「……3年前に設計したんですが……使われていなくて……」

ゆるやかな声だった。怒っていない。急いでもいない。私は心の中で「設計図さん」と呼ぶことにした。図面を広げている姿が、建築事務所の人みたいだったから。

マスターがゲスト客との会話を一段落させて、私の前にグラスを置いた。

カウンターの奥に、見慣れないボトルがある。ラベルが古い。紙の端が巻き上がっていて、他のどのボトルにもない歳月の手触りがあった。

「……それ、ずいぶん古いボトルですね」

自分から聞いている。最初の夜は「おすすめを」としか言えなかった。六杯目の夜に自分で選んで失敗した。八杯目で「なぜこれを選んだのか」が気になり、九杯目で「書かれていないこと」に気づいた。今夜は、ボトルの外見から何かを読み取ろうとしている自分がいる。

「ええ。——軽井沢でございます」

「軽井沢?」

「2000年に閉鎖された蒸留所です。新しいボトルはもう二度と作られません。今夜お出しするのは——この世に残っている、最後の数本のうちの一つです」

グラスに注がれた琥珀色は、これまでのどの夜とも違った。深い。沈んだ琥珀の中に赤みが差していて、照明を通すと宝石のようだった。

一口含んで、言葉が出なかった。複雑なのに、静か。煙の奥に果実がいて、果実の奥にスパイスがいて、スパイスの奥に——時間がいる。何年も何十年もかけて、樽の中で静かに変わり続けた時間の味がした。

「……すごい。これだけの味なのに、もう作れないの?」

「蒸留所がなくなれば——原酒を作る人も、場所も、消えます。設計図は残っていても、もうこの味は再現できません」

設計図。設計図さんが広げていた紙と、同じ言葉だった。偶然かもしれない。でも、マスターの言葉に偶然はないことを、九回の夜で学んでいる。

視線がボトルの並びを辿った。棚からカウンターへ。——取り置きボトル。麻布をかぶったまま。

一席分の距離。

もう手を伸ばすまでもなく、手が届く。麻布から覗く琥珀色が、今飲んでいる軽井沢に似ている。古いウイスキーと、布の下のウイスキー。

八杯目の夜に手を伸ばして「まだ、早いですよ」と止められた。九杯目は伸ばさなかった。今夜も伸ばさない。——でも理由が違う。前は止められたから。今夜は、「もう少し待てる自分がいる」から。

マスターが三者の場を改めた。「——お客さま、ご紹介させていただいてもよろしいですか」。設計図さんが頷く。

「こちらは常連のお客さまです」

設計図さんが軽く頭を下げた。「初めまして。——ソフトウェアのアーキテクトをやっている者です」

「アーキテクト。建築家みたいな?」

少し笑った。「ええ、ソフトウェアの設計図を描く仕事です。——ただ、今夜は……描いた設計図の話を聞いていただきたくて」

PCを開く動作がゆっくりだった。手順書さんのような焦りも、ネクタイさんのような几帳面さも、リーダーさんのような疲弊もない。設計図さんは急いでいなかった。「見てほしい」のではなく、「話を聞いてほしい」という空気。

「3年前、私がこのシステムのレポート機能を設計しました」

画面にコードが映った。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 3年前、「将来 JSON/XML 出力にも対応する」と設計された

# --- 1つのクラスでしか使われないロール ---
package Report::Role::Exportable;
use Moo::Role;

requires 'export';

has export_format => (
    is        => 'ro',
    default   => 'csv',
    predicate => 'has_export_format',  # 一度も呼ばれていない
);

# --- 子クラスが1つしかない基底クラス ---
package Report::Formatter;
use Moo;

sub format_header ($self, $columns) { ... }
sub format_row    ($self, $row)     { ... }
sub format_footer ($self)           { '' }  # 空実装

# --- 唯一のフォーマッター実装 ---
package Report::Formatter::CSV;
use Moo;
extends 'Report::Formatter';

sub format_header ($self, $columns) {
    return join(',', @$columns) . "\n";
}

sub format_row ($self, $row) {
    return join(',', @$row) . "\n";
}

# --- 本体 ---
package Report::Generator;
use Moo;
with 'Report::Role::Exportable';

has formatter => (
    is      => 'ro',
    default => sub { Report::Formatter::CSV->new },
    handles => [qw(format_header format_row format_footer)],
);

has compression => (
    is        => 'ro',
    predicate => 'has_compression',
    clearer   => 'clear_compression',
);  # 圧縮出力対応:一度も使われたことがない

sub export ($self, $data) {
    my $output = $self->format_header($data->{columns});
    for my $row ($data->{rows}->@*) {
        $output .= $self->format_row($row);
    }
    $output .= $self->format_footer;
    return $output;
}

Report::Formatter は基底クラスです。CSV、JSON、XML——将来いろんなフォーマットに対応できるように設計しました。Role::Exportable も汎用のエクスポート機能として切り出した。compression は、いつか出力を圧縮する日のために」

一呼吸。

「3年経ちました。実装されたフォーマッターは——CSVだけです」

静かに言った。後悔というより、事実を確認しているような声だった。

「……3年」

設計図さんの言葉が、胸のどこかに引っかかった。

「うちの前のCTOが——同じこと言ってたの。『将来必要になる』って作った抽象化層が3つあって。もう3年、一度も使われてない」

意識的に口にしている。先月までの私なら、他人の話に触発された独り言で済んでいただろう。でも今夜は違う。設計図さんの話が自社と重なることを認識した上で、言っている。

「……ええ」

マスターの声が、ほんの少しだけ柔らかかった。普段の「ええ」とは違う。何か馴染みのある言葉を聞いたかのような響き。——設計図さんの方を見ていたから、マスターの表情までは見えなかった。

テイスティング

マスターが画面に視線を移した。コードを上から下へ、ゆっくりたどっていく。

Report::Role::Exportable——このロールを使っているクラスは、いくつありますか」

「……一つだけです。Report::Generator だけ」

Report::Formatter を継承しているクラスは」

Formatter::CSV だけです」

compression を設定して呼び出したことは」

「一度も」

format_footer の中身は」

「……空文字を返すだけです」

短い沈黙。マスターが一つずつ並べたのは、使われていないものの一覧だった。問診のような正確さ。

「これは——Speculative Generality と呼ばれます。推測に基づく一般化」

設計図さんは驚かなかった。

「……知っています。Martin Fowler の本で読んだことがあります。『将来のために念のため作ったが、使われなかったコード』」

「お客さまは名前をご存じの上で——それでも消せずにいらっしゃる」

苦い微笑。

「……ええ。頭ではわかっているんです。後任のエンジニアに『これ消していいですか?』と聞かれて。技術的には消していい。わかっているのに——即答できなかった自分に驚きました」

設計図さんに向き直った。

「消せないのは——なぜ?」

「……いつか必要になるかもしれないから、と思っていたんですけどね。でも正直に言うと——自分が設計したものを消すのが怖いんだと思います。消したら、あの設計は間違いだったと認めることになる」

黙って聞いた。経営者として、「損切り」の難しさは知っている。うまくいかなかった事業を畳む判断の重さも。

「……わかる気がする。私も、うまくいかなかった新規事業のページをウェブサイトから消すとき、同じことを思ったことがある」

「失礼ですが——エンジニアの方ですか?」

「いいえ、会社をやってるの。でも——作ったものを手放す痛みは、同じなんじゃないかしら」

マスターが軽井沢のグラスを少し傾けた。琥珀色が照明を通す。

「この蒸留所が閉鎖されたあと——残されたボトルは倉庫を占め、管理記録が必要になり、温度と湿度の管理にコストがかかり続けました。飲まれないまま、場所と手間だけを要求し続けた」

「……維持コスト」

「ええ。使われないコードにも維持コストがかかります。新しい機能を追加する開発者は、その抽象化層を理解しなければならない。テストは動くが目的がわからないメソッドがある。コードレビューで『これは何のため?』と聞かれるたびに、誰かが説明する時間がかかる」

「使われてないのに、置いてあるだけで場所を取る……」

呟くように言った。この言葉が口をついたのは、例のフレーズ——「動いてるから」——を言おうとしたからではなかった。ただ、置いてあるだけで場所を取るものの重さが、今夜はやけにはっきりと感じられた。

「新しいエンジニアがこのシステムに参加したとき——Report::Role::Exportable を見て、何を考えるでしょうか」

設計図さんの表情が変わった。

「……『このロールを使うクラスが他にもあるはずだ』と思う。探し回る。見つからない。『なぜ1クラス用のロールがあるのか』を考え始める。そこに時間を使う」

存在するだけで、問いを生む。『これは何のため?』 『将来使うのか?』 『消していいのか?』——コードを読む人に、本来不要な認知負荷をかけ続けます」

マスターがカウンターの下からメニューの革張りのファイルを取り出した。ゲスト客に見せるのではなく、自分の手元で開いている。

指先が、メニューの一ページをなぞった。文字があった。しかし——消されている。かすかに凹みが残った跡。インクの名残。かつてカクテルの名前が書いてあった場所。

「私も——誰も注文しないカクテルを、メニューに載せていたことがあります」

十回通って初めて見る仕草だった。マスターが自分の過去について話している

「いつか誰かが頼んでくれるかもしれない、と思っていました。材料を仕入れ続け、レシピを更新し続けた。しかし——注文は、ついに来なかった」

バーに短い沈黙が落ちた。設計図さんが息を飲んでいる。マスターの言葉が、自分の話と重なったからだ。

「メニューから消すのに、二年かかりました」

目を離せなかった。マスターの声が、いつもと少し違う。穏やかさは変わらないのに、その穏やかさの質が違った。カウンター越しに誰かを諭すのではなく——自分自身に語りかけているような響き。

「——それは、いつの話ですか?」

聞いてしまった。

マスターが顔を上げた。穏やかな微笑。

「ずいぶん昔の話でございます」

それだけだった。それ以上は語らなかった。

ブレンド

設計図さんがコードに目を戻した。

「でも——消した後、本当にその機能が必要になったらどうするんですか。一から書き直しになる」

「ええ。書き直しになります」

少し驚いたように見えた。否定されると思ったのだろう。

「しかし——3年前に推測で書いたコードが、3年後の実際の要件に合致する可能性はどれほどでしょうか」

「……低い、でしょうね。3年前は想像もしなかった要件がたくさんあります」

YAGNI ——“You Aren’t Gonna Need It”。必要になるまで作るな、という原則です。これはただの怠惰ではありません。推測で作った設計は、実際の要件が判明した時点で、ほぼ必ず修正が必要になる。であれば、実際のユースケースが現れた時点で設計する方が——結果的に正確で、安価です」

「推測が外れた設計は——修正するより一から書いた方が早いこともある」

「ええ。しかもその間、推測的なコードは維持コストを払い続けています。読む人を混乱させ、認知負荷を上げ、本当に必要な変更を複雑にする」

マスターが設計図さんに向き直った。

「今のこのシステムに必要なのは——CSVレポートを出力する機能だけです」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package Report::CSVGenerator;
use v5.36;
use Moo;
use Types::Standard qw(Str);

has delimiter => (
    is      => 'ro',
    isa     => Str,
    default => ',',
);

sub generate ($self, $columns, $rows) {
    my $d = $self->delimiter;
    my @lines = join($d, @$columns);
    for my $row (@$rows) {
        push @lines, join($d, @$row);
    }
    return join("\n", @lines) . "\n";
}

設計図さんが画面を見比べている。Before と After を交互に。

「ロールが消えている。基底クラスも。フォーマッターの階層も。compressionpredicateclearer も——全部なくなっている」

「残っているのは delimiter ひとつだけです。これは現実に必要なパラメータでしょうか」

「ええ。TSV出力が必要な場面が実際にあります」

「つまり、この一つだけが——推測ではなく、実体験に基づいた設計判断です」

「前のコードと今のコードで——やってることは同じなの? CSVを出すって」

設計図さんが頷いた。「同じです。出力は変わりません」

「じゃあ何が違うの? 要らない部分を消しただけ?」

「消しただけ、です」とマスター。「しかし——その"だけ"が大きい。Report::Role::Exportable のことを思い出していただけますか」

設計図さんが頷いた。

「新しいエンジニアはあのロールを見て、『他にも使っているクラスがあるはずだ』と探し回る。見つからない。今度は Report::Formatter を見て、『CSVの他にJSONやXMLがあるはずだ』と探す。——見つからない。compression を見て、『圧縮処理を呼んでいる箇所があるはずだ』と探す。見つからない。その間ずっと、本来やるべきCSVレポートの修正は一行も進んでいません」

「在庫の棚に、ラベルのない箱が置いてあるようなもの?」

マスターがこちらを向いた。

「中身を確認するのに毎回時間がかかって、結局何も入っていないの。それが10個も20個もあったら——本当に必要な在庫が見つからなくなる」

「ええ。まさにそうです」

設計図さんがこちらを見ていた。さっきの「エンジニアの方ですか?」とは違う表情で。

「将来のために準備するのは——経営では当然のことだと思ってた。先を読んで、先に手を打つ。でもそれって、コードだと違うの?」

マスターが静かに答えた。

「経営でも同じではないでしょうか。予測が外れた先行投資にどう対処するか」

「……損切り。事業がうまくいかなかったとき、いつ撤退するかの判断」

「ええ。コードにおいても——使われていない抽象化を残しておくことは、在庫を抱え続けることと同じです。いつか使うかもしれない在庫が場所を取り、棚卸しのたびに確認が必要になる」

「……で、結局使わない」

設計図さんが静かに笑った。

「3年、使いませんでした」

ラストオーダー

設計図さんがPCをゆっくり閉じた。紙も鞄にしまった。急いでいない。

「月曜日に——後任のエンジニアに連絡します。『消していい。あれは消していい』と」

立ち上がる。マスターに一礼。

「3年、言えなかったんです。自分の設計を消していいと。——でも今夜、消された文字を見て、少し楽になりました」

メニューの消えた文字。マスターの指がなぞったあの跡を、設計図さんも見ていたのだ。

こちらに目を向けた。ゆるやかに頷いた。——経営者と設計者。立場は違うけれど、「手放す痛み」を知っている同士の、言葉のない挨拶。軽く手を上げて、扉を押して出ていった。

バーに沈黙が落ちた。軽井沢のグラスに最後の一口が残っている。

さっき飲み込んだ軽井沢の余韻が、まだ舌の奥にいる。蒸留所が消えた酒。設計図が残って、酒だけが残って、蒸留所は消えた。——うちのCTOが残した抽象化層も同じだ。設計した人は辞めて、設計だけが残っている。

「……マスター」

「はい」

「さっきの話——メニューのカクテル。あれは、バーを始める前の話ですか?」

マスターはカウンターを拭いていた。手を止めない。

「ずいぶん昔のことでございます」

同じ返し。でも私は諦めなかった。

「カクテルを、じゃなくて——何かを設計して、誰にも使われなかった経験がある。そういうことですか?」

マスターの手が、一瞬だけ止まった。ほんの一瞬。すぐにまた動き出した。

「……いかがでしょうか。今夜の軽井沢は、お口に合いましたか」

話をそらしている。穏やかに、しかし確実に。十回通えば、マスターが話をそらすときの空気くらいは読めるようになる。

沈黙。まっすぐマスターを見た。

「マスター。——うちのコードを見てほしいんです」

声は震えていなかった。不安でも衝動でもない。九回の夜を通って、ここまで来た。設計図さんの「消していい」。マスターの「メニューから消すのに二年かかった」。使われないものを手放す勇気の話を聞いた夜に——自分の会社のコードに向き合うことを、決めた。

「前のCTOの抽象化層。3年使われてないやつ。辞めたCTOしか知らない設定。手順書がないとデプロイできないこと。矢印がたくさん並んでるのも。——全部、名前がある種類の問題なんでしょう?」

九回分の断片が口をついて出た。正確じゃないかもしれない。名前を間違えているかもしれない。でも、「問題がある」と知っている。「匂い」がわかるようになったことを、今夜はっきりと自覚していた。

マスター、カウンターを拭く手を止めた。こちらを見た。穏やかだが、いつもとほんの少し違う目。九回見てきた接客の顔ではない何かが、一瞬だけ透けて見えた——ような気がした。

「もう少しだけ、待ちましょう」

「……なぜ?」

「あと二杯分、お付き合いいただけますか」

答えになっていない。でも、拒絶でもなかった。——「待ちましょう」と言ったのは、待つ先に何かがあるからだ。

グラスを空にして、カウンターに置いた。

視線が取り置きボトルへ行く。一席分の距離。もうすぐ手が届く。麻布の下の琥珀色が、さっき飲んだ軽井沢に似ている気がした。

「このボトルも——いつか飲めるんですか」

「ええ。もう少しだけ」

同じ言葉。コードを見てほしいと頼んだときと、同じ返し。

——あのボトルと、私のコードは、どこかで繋がっているのだろうか。

「おやすみなさい、マスター」

「おやすみなさいませ。お気をつけて」

扉を押した。路地裏の夜風。

マスターはカクテルを消すのに二年かかったと言った。設計図さんは三年越しで「消していい」と言えるようになった。——私は、どれだけかかるんだろう。

でも今夜、「見てほしい」と言えた。それだけは確かだ。

「もう少しだけ」。あと二杯。マスターが何を待っているのかはわからない。でも——九回待ったのだから、あと二杯くらいは待てる。

路地裏を歩きながら、軽井沢の余韻を舌で探した。もう二度と作れない酒の味。——でも味を覚えている。覚えていることには、意味があるはずだ。


🥃 マスターのテイスティングノート

本日の銘柄: 軽井沢(終売蒸留所)
お客さまの症状: 推測的汎化(Speculative Generality)

ノージング(香り)── 問題の検知

実装が1つしかない抽象クラス。1つのクラスでしか使われていないロール。predicateclearer が定義されているのに一度も呼ばれていない属性。空の format_footer。——「将来使うかもしれない」という推測の残り香が漂っていたら、Speculative Generality を疑いましょう。

パレット(味わい)── 問題の本質

使われないコードにも維持コストがかかります。新しいメンバーは「これは何のため?」と問い、答えが返ってこない。認知負荷が上がり、コードレビューのたびに「消していいのか」が議論され、結局誰も手をつけない。存在するだけで問いを生む——それが推測的汎化の味です。

フィニッシュ(余韻)── 解決の方針

YAGNI ——必要になるまで作らない。不要な基底クラスを消し、ロールを元のクラスに戻し、使われていない属性を削除する。推測で設計した抽象化は、実際の要件に出会ったときにほぼ必ず修正が必要です。必要になった時点で作る方が、正確で安価です。

ペアリング(相性の良いパターン)

  • YAGNI 原則(You Aren’t Gonna Need It)
  • Collapse Hierarchy(階層の折りたたみ)
  • Three Strikes and You Refactor(3回則)

「設計図は残っていても——蒸留所がなくなれば、もうあの味は再現できません」

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。