ESLint: no-magic-numbers についての「くだらない話」

goto は悪だ、にも通じるところもあるのだけれど。

一つ前何個か前で「わかりやすい主張には皆熱中しやすい」と書き、そこで「マジックナンバーへの過度な反応」とさらっと書いたので、そのことについても書いておこうと思う。

「マジックナンバーは悪だ」の主張は、本来のものは非常にわかりやすい:

1 return 31536000 + is_leap * 86400;

これが「31536000 てなんだよ、86400 てなんだよ、わかるように書けやこのばかちんが」というのが、それこそ「みんな大好きカーニハン大明神さまさま」だとかロブ・パイクだとか、とにかく「色んな教育熱心な先達」によって、口うるさく言われてきた、「伝統芸能」である。無論このことに異論などあるはずがなく、「せんせー、大好きっ」である。

そして「だからこそ」、皆「絶対視し、根絶に夢中になる」。ほかのもっと根幹にかかわる大事な大事なことを「ほっぽらかしてでも」。

それこそワタシが共感しないと言ったばかりの JSHint のポリシーに関係するのだが、まず「Correctness (正しさ)」の話なのか「Maintainability (保守性)」の話なのかをきちんと区別することが必要である。

「保守性が欠けるプログラムは正しさも危うい」というふうにこれらを一体と考えるのが、いわばスタイルチェッカの責務なわけで、そのことで JSHint を非難したわけだが、だからといって「正しさ」と「保守性」の優先度が「ない」と考えるのもこれは違う。そして「マジックナンバーの件」は「保守性の問題である」と考えることが出来ないエンジニアがなぜか多いのも気になる。

問題の話はここから。つまり、「根絶には夢中になる」割には、その本来の「保守性の問題」に全然意識的になれないエンジニアが非常に多いのだ。まず、「誰がいつどのようなタイミングで「保守」を行うのか」の想像力を欠いたまま、「定数は一箇所にかき集めれば便利」ルールに従って、一枚岩の「バカでかい定数かき集めファイル」を発明する。無論このアプローチにも正当な理由がないことはないが、「保守性」を考えた結果そうするのではなく、「それしか思いつかない」からそうするわけである。だいたいにして、その種の「マジックナンバー」は、「全体で共有されるべき知識」である場合もあれば、そうでない場合も多い。つまり、「特定の処理にだけ秘匿されるべきマジックナンバー」はあるのである、多いのである。

非常にわかりやすい例。まず、「日付時刻を扱うユーティリティ」というものを書きたい、とする。なおかつ、「アプリケーションの振る舞いを変えるためのプロパティのデフォルト」を管理したいとする。しかるに「定数は一箇所にまとめなければならない」に従うならばこうなる:

java の例。冗談ではなくこんなのが 1K、2K なんて規模になるなんてザラである。
 1 public class ApplicationConst {
 2     private void ApplicationConst() {}
 3 
 4     // --- 日付時刻関連 BEGIN
 5     public static final int TOTAL_SECONDS_OF_YEAR = 31536000;
 6     public static final int TOTAL_SEDONDS_OF_DAY = 86400;
 7     // ... 以下大量に…
 8 
 9     // --- 日付時刻関連 END
10 
11     // ...ほかにもゴロゴロ大量に
12 
13     // --- アプリケーション設定デフォルト関連 BEGIN
14     public static final Point DEFAULT_START_POSITION = ...;
15     public static final Point DEFAULT_WINDOW_WIDTH = ...;
16     public static final Point DEFAULT_WINDOW_HEIGHT = ...;
17     // ... 以下大量に…
18 
19     // --- アプリケーション設定デフォルト関連 END
20 
21     // ...ほかにもわんさか様々に
22 
23     // --- 共通 BEGIN
24     // 以下使用は非推奨だが、どうしても名前が付けられない場合に限り、checkstyle を静かにするのに
25     // 利用してよい。(本質が埋もれるよりはマシ。)
26     public static final int ONE = 1;
27     public static final int TWO = 2;
28     public static final int THREE = 3;
29     // ... 以下「膨大に」…
30 
31     // --- 共通 END
32 };

こうした「管理」が「保守性」が良いか悪いかなんてことは「少し考えればすぐにわかる」ことなのに、こういうことをするエンジニアは自分の正義を疑わないので、たとえ VCS でエンジニア間の更新のコンフリクトが「毎日」発生しようが、一向に気にしない。

今の例は「保守性」の話でもあるが、「一箇所に集める」という発想そのものが「カプセル化のポリシーに反する」ことが「なぜだか忘れ去られる」ということも表している。すなわち、今の場合「日付時刻関連」は「日付時刻関連の処理」に閉じ込めてしまうべき知識なのである。おそらく「一箇所触るだけで全体が従ってくれる」というメリットをどこかで中途半端に聞きかじるからこれ「だけが正義」になるのだろうが、なぜそれだけで「みんな大好きオブジェクト指向」の根幹を揺るがすような破壊をしていることに気づかないのかは、ほんと理解に苦しむ。(言っちゃ悪いがそういうのに限って「おぶじぇくとしこうまんせー、java まんせー」タイプである。)

そしてこれも以前書いたことがあるが、「最終的にその「定数」を誰がどのように管理するのか」(保守)について意識的でなければ元来おかしいわけである。多くは「保守担当エンジニア」だろうが、「顧客」の場合もある。が、「顧客」に「java ソースを触らせて、コンパイルもさせる」という「設計」をする「わけがない」のであって、そうしたものは「設定ファイル」に書き出しておくのが普通である。そしてかなりのバカエンジニアが「円周率」を設定ファイルに書き込むのだ。それはあんたの持ち物ではない。

もう一つ。「定数だ」⇒「const にして定数ファイルにかき集めよ」。ヲィ。「円周率」を例にしたのはわけがある。つまり、「既にあるのに」発明してしまうバカもこれは「とてつもなく多い」。そしてそれが保守性を悪くしていることにも気付かずに、「定数ファイルに追い出すオレ、ちゃんとしてるぜっ」と譲らない。

ワタシが「過度の反応」と呼ぶのはこの構造である。「マジックナンバーは悪だ」は「保守性を悪くするからだ」という「意味」を理解しないまま闇雲に絶対視するからこんなことになる。そして「悪は悪だがもっと大事なことがある」ことを忘れてしまうのは、ひとえに「あまりにもわかりやすく、昔から言われ過ぎているから」なのかもしれない。