Featured image of post コードバーテンダー【Magic Numbers】105の名を持つ琥珀〜意味を失ったリテラルの行方〜

コードバーテンダー【Magic Numbers】105の名を持つ琥珀〜意味を失ったリテラルの行方〜

マジックナンバーとは何か? Perl+Mooのコード例で、裸のリテラル値が引き起こす問題と、名前付き定数・設定値注入による解決策を物語形式で解説します。

私がこのバーに通い始めたのは、コードの匂いなんてまるでわからなかった頃のことだ。

その日、看板のない路地裏の店を教えてくれたのは、取引先の営業担当だった。「面白い人がいるんですよ、コードの話をウイスキーで解く人が」。半信半疑で重い木の扉を押すと、カウンターだけの小さな空間が広がっていた。磨き上げられた一枚板のカウンターに指先で触れると、すべすべした木目の感触が心地よかった。壁一面に並ぶウイスキーのボトル、そしてその向こうに、白いシャツに黒のベストを着た男が立っている。

「いらっしゃいませ。今夜は何をお召し上がりになりますか」

穏やかな声だった。私はボトルの壁を見上げて、少し笑った。

「おすすめを」

何を選べばいいかなんて、わからなかったから。

来店——105という名の琥珀

マスターがカウンターの奥へ手を伸ばし、一本のボトルを取った。琥珀色の液体をグラスに注ぐ。濃い、力強い色だ。氷を入れないストレート。グラスを目の前に置かれた瞬間、甘く、どこか焦がしたような濃厚な香りが鼻先をかすめた。

「グレンファークラス 105でございます」

「105? お酒の名前が数字なの?」

「ブリティッシュプルーフで105。アルコール度数にすると60%——つまり、この琥珀色の液体の強さを、数字だけで名付けたお酒です。今夜はストレートで」

一口含んだ。甘い果実のような香りのあとに、強烈な熱が喉を走り抜けた。目が潤む。

「……つよい」

「ええ。ですが——105という数字だけを聞いて、この味を想像できる方はほとんどいません」

マスターの言葉の意味は、そのときの私にはわからなかった。

余韻が舌に残っているうちに、扉が軋んだ。振り返ると、スーツの上着を脱いでネクタイを緩めた若い男性が立っていた。二十代前半に見える。途方に暮れた顔で、肩からノートPCの入ったバッグを提げている。

「あの、ここって予約とかいりますか」

「いいえ。どうぞ、お好きな席に」

新人くん——と私は心の中で呼んだ。入社したばかりの頃のうちのエンジニアに、少し雰囲気が似ていたから。

彼はカウンターの端にぎこちなく座り、しばらく黙っていた。マスターが水の入ったグラスを置くと、せきを切ったように話し始めた。

「あの、いきなりで変な話なんですけど……コードが読めないんです。自分の先輩が書いたコードが」

聞けば、入社二年目。半年前に先輩が退職し、引き継いだシステムのコードが数字だらけで、何をしているのかわからないという。

新人くんはバッグからノートPCを取り出し、画面をマスターに向けた。

if文の中に36とか72とか3600が並んでて。先輩に聞こうにも、もういなくて」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package SubscriptionService;
use v5.36;
use Moo;

sub check_access ($self, $user) {
    if ($user->days_since_signup <= 36) {
        return 1;
    }
    if ($user->last_login_minutes < 72) {
        return 1;
    }
    if ($user->session_age > 3600) {
        $user->logout;
        return 0;
    }
    return $user->is_paid ? 1 : 0;
}

「36って何の36なんですか、って聞ける人がもういないんです」

私はグラスの105を見た。数字だけの名前のお酒。そういえば。

「……うちのシステムにも、36とか72とか、謎の数字がいっぱいあるのよね」

なぜそんなことを口にしたのか、自分でもわからない。新人くんの話を聞いていたら、なんとなく浮かんだだけだった。

「動いてるからいいのよね」

マスターがグラスを拭く手を止めた。ほんの一拍。カウンターの向こうから、静かにこちらを見た気がした。たぶん、気のせいだ。

テイスティング——ラベルのないボトル

「お客さま」

マスターが新人くんに向き直った。

「コードに書かれた36という数字。これは無料トライアルの日数でしょうか、それともセッションの有効期限でしょうか」

新人くんは考え込んだ。

「えっと……たぶんトライアル日数、だと思うんですけど……。72は何だろう。3600は秒だから1時間? でも確証はないです」

「お客さまのコードは、Magic Numbers——マジックナンバーと呼ばれる状態です」

マジックナンバー。聞いたことのない言葉だった。マスターは続けた。

「コードの中に裸のリテラル値——つまり、数字がそのまま書かれていて、その意味が文脈から読み取れない状態を指します」

私は思わず新人くんに聞いた。

「ねえ、書いた人がいなくなったら、もう誰にもわからないの? ドキュメントとかないの?」

新人くんは力なく首を振った。「仕様書もないんです。コメントもなくて」

マスターが棚のボトルを一本指さした。ラベルが剥がれたボトルだった。

「ラベルのないボトルは、中身を知る人がいなくなったとき、ただの液体になります」

新人くんが小さくうなずいた。「まさにそれです。先輩がいなくなって、36がただの数字になった」

なぜ問題なのか

マスターはカウンターに指で3つの点を打った。

「マジックナンバーの問題は、3つあります」

「まず、同じ数値でも意味が違うこと。36がトライアル日数だとして、別のファイルにある36がグリッドの列数だったら——一括置換した瞬間に、レイアウトも壊れます」

「次に、値を変更したいときの手間。トライアル期間を30日に変えるとき、すべてのファイルを検索して《この36はどの36か》を一つずつ判断しなければなりません」

「最後に、タイポが検出できないこと。3600360と書き間違えても、プログラムはそのまま動きます。ただし、セッションが1時間ではなく6分で切れるようになります」

新人くんが頭を抱えた。「それ、うちのシステムで起きてたらどうしよう……」

私は横から新人くんに言った。「6分でセッション切れたら、お客さん怒るわよね、さすがに」

新人くんが苦笑いした。「怒るどころじゃないですよ。問い合わせ殺到です」

ブレンド——名前を付けるということ

「最初の一歩は、名前を付けることです」

マスターはおもむろにカウンターの下からノートを取り出し、何かを書き留めた。革表紙の手帳——テイスティングノートに見えた。書き終えると、新人くんに向き直った。

「Perlではuse constantで名前付き定数を宣言できます」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package SubscriptionService;
use v5.36;
use Moo;

use constant {
    FREE_TRIAL_DAYS       => 36,
    ACTIVE_WINDOW_MINUTES => 72,
    SESSION_TIMEOUT_SEC   => 3600,
};

sub check_access ($self, $user) {
    if ($user->days_since_signup <= FREE_TRIAL_DAYS) {
        return 1;
    }
    if ($user->last_login_minutes < ACTIVE_WINDOW_MINUTES) {
        return 1;
    }
    if ($user->session_age > SESSION_TIMEOUT_SEC) {
        $user->logout;
        return 0;
    }
    return $user->is_paid ? 1 : 0;
}

新人くんは画面を見比べている。

「でも、値は同じですよね? 何が変わったんですか?」

「3つのことが変わりました」

マスターはグレンファークラスのボトルを持ち上げ、ラベルを指でなぞった。

一つ目36FREE_TRIAL_DAYSになったことで、このコードを初めて読む人にも意味が伝わります。ラベルが貼られたボトルです」

二つ目。トライアル期間を30日に変えたければ、定数の定義を1箇所変えるだけで済みます。ラベルの裏側に産地が書いてある——一箇所を直せば、すべての文脈で正しくなる」

三つ目FREE_TRILA_DAYSと書き間違えたら、コンパイルエラーになります。3663と間違えても、Perlは何も言いません」

新人くんの目が明るくなった。「あ、テストでFREE_TRIAL_DAYSって書けば、何をテストしてるかもわかる……!」

もう一歩先へ

「もう一歩、踏み込んでもよろしいでしょうか」

マスターはボトルをカウンターに戻し、別の角度から語り始めた。

「定数は定義したファイルの中に閉じ込められます。テストのときだけトライアル期間を1日にしたい——そんなとき、定数では対応できません」

「Mooのhasを使えば、値をオブジェクトの外から注入できます」

 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
package SubscriptionService;
use v5.36;
use Moo;
use Types::Standard qw(Int);

has free_trial_days => (
    is      => 'ro',
    isa     => Int,
    default => 36,
);

has active_window_minutes => (
    is      => 'ro',
    isa     => Int,
    default => 72,
);

has session_timeout_sec => (
    is      => 'ro',
    isa     => Int,
    default => 3600,
);

sub check_access ($self, $user) {
    if ($user->days_since_signup <= $self->free_trial_days) {
        return 1;
    }
    if ($user->last_login_minutes < $self->active_window_minutes) {
        return 1;
    }
    if ($user->session_age > $self->session_timeout_sec) {
        $user->logout;
        return 0;
    }
    return $user->is_paid ? 1 : 0;
}

isa => Intとすることで、文字列や小数が渡された瞬間にエラーになります。値の《意味》と《制約》が、コードに刻まれるわけです」

新人くんが身を乗り出した。「テストのときだけ値を変えられるってことですか?」

「ええ。SubscriptionService->new(free_trial_days => 1)と書けば、1日のトライアル期間でテストできます。本番のデフォルト値は変わりません」

「うわ、テスト書くとき毎回困ってたんです、固定値が変えられなくて!」

私はウイスキーのグラスを傾けながら、なんとなく思った。ラベルの話は経営でも同じだ。名前のない予算項目は誰も管理しない。——でも、それはただの感想。技術の話は、まだ私には遠い世界のことだった。

ラストオーダー——お取り置きです

新人くんは晴れやかな顔で立ち上がった。

「明日、まず定数に名前つけるところから始めます。ありがとうございました」

ノートPCをバッグにしまいながら、彼は私にも軽く頭を下げた。「ありがとうございます、お話に付き合っていただいて」

「頑張ってね」

扉が閉まり、店内に静けさが戻った。グラスの氷が溶ける音が、妙にはっきり聞こえた。

「あの子、嬉しそうだったわね」

「名前がつくと、問題ではなく、課題になりますから」

マスターの声は穏やかだった。

ふと、カウンターの端に目がいった。最も遠い位置に、麻布をかぶった一本のボトルが置いてある。さっきは気づかなかった。

「あのボトルは?」

「お取り置きです」

微笑むだけで、それ以上は何も言わない。

マスターがカウンターの下からあのノートを取り出し、何かを書き留めていた。

「お客さまの好みを記録しているのですか?」

「ええ、そのようなものです」

そうして私は店を出た。路地裏の夜風が肌に涼しく、扉の向こうからかすかにグラスの触れ合う音が聞こえた。なぜかもう一度この店に来たいと思った。

あの夜、私はまだ何も知らなかった。36という数字が、自分の会社に何をもたらしているのかも。


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

本日の銘柄: グレンファークラス 105
お客さまの症状: マジックナンバー(Magic Numbers)

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

コードの中に36, 72, 3600のような裸のリテラルが散在していたら、このアンチパターンを疑いましょう。「なぜその値なのか」がコードから読み取れないとき、それはラベルのないボトルです。

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

数字に名前がないということは、その意味が書いた人の頭の中にしかないということです。同じ36でもトライアル日数とグリッド列数では意味が違う。一括置換の事故、タイポの見逃し、そして「書いた人がいなくなったら誰にもわからない」——マジックナンバーは、コードから文脈を奪います。

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

use constantで名前を付けるのが第一歩。さらにMooのhasTypes::StandardIntで値をオブジェクトに注入すれば、テスト時の差し替え、型による不正値の検出、デフォルト値の一元管理が手に入ります。ラベルを貼ることで、「ただの数字」は「意味のある設定値」に変わります。

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

  • Primitive Obsession(プリミティブ型偏執)—— 定数化の次のステップとして、値をオブジェクトに昇格させる
  • Types::Standard による型制約 —— EnumIntで値の範囲と意味をコードに刻む
  • Single Responsibility Principle —— 定数値の管理責任を明確にする

「ラベルのないボトルは、中身を知る人がいなくなったとき、ただの液体になります」

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