マジックナンバーとは?
なぜそれを避けるべきですか?
該当するケースはありますか?
マジック ナンバーは、コード内の数字を直接使用したものです。
たとえば、(Java で) 次の場合:
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
これは次のようにリファクタリングする必要があります。
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
コードの可読性が向上し、保守が容易になります。GUI でパスワード フィールドのサイズを設定する場合を想像してみてください。マジック ナンバーを使用すると、最大サイズが変更されるたびに、2 つのコードの場所を変更する必要があります。1つ忘れると、矛盾が生じます。
JDK にはInteger
、Character
、Math
クラスなどの例がたくさんあります。
PS: FindBugs や PMD などの静的分析ツールは、コード内のマジック ナンバーの使用を検出し、リファクタリングを提案します。
マジック ナンバーはハードコードされた値であり、後の段階で変更される可能性がありますが、更新が難しい場合があります。
たとえば、「あなたの注文」概要ページに最新の 50 件の注文を表示するページがあるとします。ここでの 50 はマジック ナンバーです。これは、標準または慣習によって設定されたものではないため、仕様に記載されている理由で作成した数字です。
ここで、SQL スクリプト ( SELECT TOP 50 * FROM orders
)、Web サイト (最後の 50 件の注文)、注文ログイン ( for (i = 0; i < 50; i++)
) など、さまざまな場所に 50 を配置します。
では、誰かが 50 を 25 に変更するとどうなるでしょうか? または75?または153?すべての場所で 50 を交換する必要があり、見逃す可能性が非常に高くなります。50 は他の目的で使用される可能性があり、盲目的に 50 を 25 に置き換えると、他の悪い副作用が発生する可能性があるため、検索/置換が機能しない場合があります (つまり、Session.Timeout = 50
呼び出しも 25 に設定され、ユーザーが頻繁にタイムアウトを報告し始める)。
また、" " などのように、コードが理解しにくいif a < 50 then bla
場合があります。複雑な関数の途中でこれに遭遇した場合、コードに慣れていない他の開発者は、「WTF は 50 ですか?」と自問する可能性があります。
そのため、このようなあいまいで恣意的な数字は、正確に 1 か所 (" const int NumOrdersToDisplay = 50
") に配置するのが最善です。これは、コードをより読みやすくするためです (" if a < NumOrdersToDisplay
"。これは、明確に定義された 1 か所だけを変更する必要があることも意味します。
マジック ナンバーが適切な場所は、標準によって定義されているすべてのものです。つまりSmtpClient.DefaultPort = 25
、またはTCPPacketSize = whatever
(標準化されているかどうかは不明です)。また、1 つの関数内でのみ定義されたすべてが許容される場合もありますが、それは Context によって異なります。
ウィキペディアのマジック ナンバーのエントリをご覧になりましたか?
マジック ナンバーの参照が行われるすべての方法について少し詳しく説明します。これは、プログラミングの悪い習慣としてのマジックナンバーに関する引用です
マジック ナンバーという用語は、ソース コード内で説明なしに数値を直接使用するプログラミングの悪い慣行も指します。ほとんどの場合、これによりプログラムの読み取り、理解、保守が難しくなります。ほとんどのガイドでは数字の 0 と 1 を例外としていますが、コード内の他のすべての数字を名前付き定数として定義することをお勧めします。
魔法: 未知のセマンティック
シンボリック定数 -> 正しいセマンティックと正しいコンテキストの両方を使用できるようにします
セマンティック: 物事の意味または目的。
「定数を作成し、意味に基づいて名前を付け、数字をそれに置き換えます。」-- マーティン・ファウラー
まず、マジック ナンバーは単なる数字ではありません。どんな基本的な値でも「魔法」になることができます。基本値は、整数、実数、倍精度浮動小数点数、日付、文字列、ブール値、文字などのマニフェスト エンティティです。問題はデータ型ではなく、コード テキストに表示される値の「魔法」の側面です。
「魔法」とはどういう意味ですか?正確に言えば、「魔法」によって、コードのコンテキストにおける値のセマンティクス (意味または目的) を示すつもりです。不明、不明、不明確、または紛らわしいこと。これが「魔法」の概念です。基本的な値は、そのセマンティックな意味または存在の目的が、特別なヘルパー ワード (たとえば、記号定数) なしでサラウンド コンテキストからすばやく簡単に認識され、明確で、理解される (混乱しない) 場合、魔法ではありません。
したがって、コードリーダーが基本的な値の意味と目的を周囲のコンテキストから認識し、明確にし、理解する能力を測定することにより、マジックナンバーを識別します。読者があまり知られていない、明確でない、混乱しているほど、基本的な価値はより「魔法」です。
魔法の基本値には 2 つのシナリオがあります。プログラマーとコードにとって最も重要なのは 2 番目だけです。
「魔法」の包括的な依存関係は、単一の基本値 (数値など) が一般的に知られているセマンティック (Pi など) を持たないが、ローカルで知られているセマンティック (プログラムなど) を持っている方法であり、コンテキストから完全に明確ではないか、悪用される可能性があります。良いまたは悪い文脈で。
ほとんどのプログラミング言語のセマンティクスでは、(おそらく) データ (つまり、データのテーブル) としての場合を除いて、単一の基本値を使用することはできません。「マジック ナンバー」に出くわすとき、私たちは通常、文脈の中でそうします。したがって、への答えは
「このマジックナンバーを記号定数に置き換えますか?」
は:
「文脈の中で数字の意味(そこにある目的)をどれだけ早く評価して理解できるか?」
この考えを念頭に置くと、Pi (3.14159) のような数値が、適切なコンテキスト (例: 2 x 3.14159 x 半径または 2*Pi*r) に配置された場合、「魔法の数」ではないことがすぐにわかります。ここで、数値 3.14159 は、記号定数識別子のない、精神的に認識される Pi です。
それでも、通常は 3.14159 を Pi のような記号定数識別子に置き換えます。これは、数値の長さと複雑さのためです。Pi の長さと複雑さの側面 (正確さの必要性と相まって) は、通常、記号識別子または定数がエラーを起こしにくいことを意味します。名前としての「Pi」の認識は単に便利なボーナスですが、定数を持つ主な理由ではありません。
Pi のような一般的な定数はさておき、主に特別な意味を持つ数値に焦点を当てましょう。ただし、それらの意味はソフトウェア システムの世界に限定されています。そのような数は「2」かもしれません (基本的な整数値として)。
数字の 2 を単独で使用すると、最初の質問は次のようになります。「2」はどういう意味ですか? 「2」自体の意味は不明であり、文脈がなければ知ることができないため、その使用は不明確で混乱を招きます. 言語のセマンティクスのために、ソフトウェアに「2」だけを含めることはできませんが、「2」自体には特別なセマンティクスや明確な目的がないことを確認したいと考えています。
唯一の "2" を のコンテキストに入れましょう。padding := 2
ここで、コンテキストは "GUI コンテナー" です。このコンテキストでは、2 の意味 (ピクセルまたは他のグラフィック単位として) は、そのセマンティクス (意味と目的) の簡単な推測を提供します。ここで立ち止まって、このコンテキストでは 2 で十分であり、他に知る必要があることは何もないと言うかもしれません。しかし、おそらく私たちのソフトウェアの世界では、これがすべてではありません。他にもありますが、コンテキストとしての「パディング = 2」では明らかになりません。
さらに、プログラムのピクセル パディングとしての 2 は、システム全体で "default_padding" の種類であると仮定しましょう。したがって、指示を書くpadding = 2
だけでは十分ではありません。「デフォルト」の概念は明らかにされていません。私が書いたときだけ:padding = default_padding
コンテキストとして、そして他の場所で:default_padding = 2
私たちのシステムにおける2のより良い、より完全な意味(意味と目的)を完全に理解します.
上記の例は、"2" だけで何でもかまいません。理解の範囲とドメインを「私のプログラム」に限定した場合にのみ、2 は「私のプログラム」default_padding
の GUI UX 部分にある場合にのみ、適切なコンテキストで「2」を最終的に理解することができます。default_padding
ここで、「2」は「魔法の」数であり、「私のプログラム」の GUI UX のコンテキスト内でシンボリック定数に分解されdefault_padding
、それを囲んでいるコードのより大きなコンテキストですぐに理解できるように使用できるようにします。
したがって、その意味 (意味と目的) が十分かつ迅速に理解できない基本値は、基本値 (マジック ナンバーなど) の代わりに記号定数の良い候補となります。
スケール上の数字にもセマンティクスがある場合があります。たとえば、モンスターの概念がある D&D ゲームを作成しているとします。モンスター オブジェクトにlife_force
は、整数である という機能があります。数字には、意味を与える言葉がないと認識できない、または明確でない意味があります。したがって、私たちは恣意的に次のように言うことから始めます。
上記の記号定数から、D&D ゲームにおけるモンスターの生存、死亡、および「不死」(および考えられる影響または結果) の精神的なイメージを取得し始めます。これらの単語 (記号定数) がなければ、 から の範囲の数字だけが残り-10 .. 10
ます。attack_elves
ゲームのさまざまな部分が、や などのさまざまな操作に対してその範囲の数値が何を意味するかに依存している場合、単語のない範囲だけでは、ゲームで大きな混乱が生じたり、エラーが発生したりする可能性がありますseek_magic_healing_potion
。
したがって、「マジック ナンバー」の置換を検索して検討するときは、ソフトウェアのコンテキスト内で数字について、さらには数字が意味的に相互にどのように相互作用するかについて、非常に目的に満ちた質問をしたいと考えています。
どのような質問をするべきかを確認しましょう。
次の場合は、マジック ナンバーを持っている可能性があります。
コード テキスト内のスタンドアロン マニフェスト定数の基本値を調べます。そのような値の各インスタンスについて、ゆっくりと思慮深く各質問をしてください。あなたの答えの強さを考慮してください。多くの場合、答えは白黒ではありませんが、誤解されている意味と目的、学習速度、理解速度の色合いがあります. また、周囲のソフトウェア マシンにどのように接続するかを確認する必要もあります。
結局のところ、置き換えに対する答えは、読者の強みまたは弱みを(あなたの頭の中で)測定して、接続を確立することです(たとえば、「理解してください」)。彼らが意味と目的を理解するのが早ければ早いほど、あなたの「魔法」は少なくなります。
結論: 基本的な値をシンボリック定数に置き換えるのは、混乱から生じるバグを検出するのが困難なほどマジックが大きい場合に限ってください。
マジック ナンバーは、ファイル形式またはプロトコル交換の先頭にある一連の文字です。この番号は、健全性チェックとして機能します。
例: GIF ファイルを開くと、最初に GIF89 が表示されます。「GIF89」はマジックナンバーです。
他のプログラムは、ファイルの最初の数文字を読み取って、GIF を適切に識別できます。
危険なのは、ランダムなバイナリ データにこれらの同じ文字が含まれる可能性があることです。しかし、その可能性は非常に低いです。
プロトコル交換に関しては、これを使用して、渡されている現在の「メッセージ」が破損しているか無効であることをすばやく特定できます。
マジックナンバーは今でも役に立ちます。
プログラミングでは、「マジック ナンバー」は記号名を付ける必要がある値ですが、通常は複数の場所でリテラルとしてコードに組み込まれています。
SPOT (Single Point of Truth) が優れているのと同じ理由で、これは良くありません。後でこの定数を変更したい場合は、すべてのインスタンスを見つけるためにコードを調べなければなりません。また、この数値が何を表しているのかが他のプログラマーには明らかでない可能性があるため、これも悪いことです。つまり、「魔法」です。
これらの定数を別のファイルに移動して構成として機能させることにより、マジックナンバーの排除をさらに進めることがあります。これは役立つ場合もありますが、必要以上に複雑になる可能性もあります。
マジックナンバーの使用で言及されていない問題...
それらが非常に多い場合は、魔法数を使用する2つの異なる目的があり、値が同じである可能性がかなり高くなります。
そして、確かに、値を変更する必要があります...1つの目的のためだけに。
マジックナンバーは、特別なハードコードされたセマンティクスを持つ番号にすることもできます。たとえば、レコードID> 0が正常に処理され、0自体が「新しいレコード」、-1が「これはルートです」、-99が「これはルートで作成された」というシステムを見たことがあります。0および-99を指定すると、WebServiceは新しいIDを提供します。
これの悪い点は、特別な能力のためにスペース(レコードIDの符号付き整数のスペース)を再利用していることです。ID 0または負のIDでレコードを作成したくない場合もありますが、そうでない場合でも、コードまたはデータベースのいずれかを見るすべての人がこれに遭遇し、最初は混乱する可能性があります。言うまでもなく、これらの特別な値は十分に文書化されていませんでした。
間違いなく、22、7 、-12、および620もマジックナンバーとしてカウントされます。;-)
これは、以前の質問に対する私の回答に対する回答だと思います。プログラミングでは、マジック ナンバーは、説明なしに表示される埋め込み数値定数です。2 つの異なる場所に表示される場合、1 つのインスタンスが変更され、別のインスタンスが変更されないという状況につながる可能性があります。これらの両方の理由から、数値定数を使用される場所の外に分離して定義することが重要です。
私は常に「マジックナンバー」という用語を別の方法で使用していました。これは、データ構造内に格納されているあいまいな値であり、迅速な有効性チェックとして検証できます。たとえば、gzipファイルには最初の3バイトとして0x1f8b08が含まれ、Javaクラスファイルは0xcafebabeで始まります。
ファイルはかなり無差別に送信され、作成方法に関するメタデータが失われる可能性があるため、ファイル形式にマジックナンバーが埋め込まれていることがよくあります。ただし、魔法数は、ioctl()呼び出しのように、メモリ内のデータ構造にも使用されることがあります。
ファイルまたはデータ構造を処理する前にマジックナンバーをすばやくチェックすることで、入力が完全なバルダーダッシュであったことを通知するために、潜在的に長い処理をすべて実行するのではなく、エラーを早期に通知できます。
コードに設定不可能な「ハードコードされた」数値が必要な場合があることに注意してください。最適化された逆平方根アルゴリズムで使用される 0x5F3759DF を含む有名なものがいくつかあります。
そのようなマジック ナンバーを使用する必要があるとわかったまれなケースでは、それらをコード内で const として設定し、それらが使用される理由、機能、およびそれらがどこから来たのかを文書化します。
クラスの先頭にある変数をデフォルト値で初期化するのはどうですか? 例えば:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
この場合、15000 はマジック ナンバーです (CheckStyles による)。私にとっては、デフォルト値を設定しても問題ありません。私はする必要はありません:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
そうなると読みづらくなりますか?CheckStyles をインストールするまで、これを考えたことはありませんでした。
戻り変数はどうですか?
ストアド プロシージャを実装するときは特に難しいと思います。
次のストアド プロシージャを想像してみてください (例を示すためだけに、構文が間違っていることはわかっています)。
int procGetIdCompanyByName(string companyName);
特定のテーブルに存在する場合、会社の ID を返します。それ以外の場合は -1 を返します。なんというかマジックナンバーです。これまでに読んだいくつかの推奨事項では、次のような設計を実際に行う必要があると述べています。
int procGetIdCompanyByName(string companyName, bool existsCompany);
ところで、会社が存在しない場合、何を返せばよいのでしょうか?Ok: existsesCompanyをfalseに設定しますが、-1 も返します。
Antoher オプションは、2 つの別個の関数を作成することです。
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
したがって、2 番目のストアド プロシージャの前提条件は、会社が存在することです。
しかし、このシステムでは、別のユーザーが会社を作成できるため、並行性が心配です。
結論としては、比較的知られており、何かが失敗したり、何かが存在しないことを安全に判断できるような「マジック ナンバー」を使用することについてどう思いますか?
@ eed3si9n: '1' はマジック ナンバーだと思います。:-)
マジック ナンバーに関連する原則は、コードが扱うすべての事実を 1 回だけ宣言する必要があるということです。コードでマジック ナンバーを使用する場合 (@marcio が提供したパスワードの長さの例など)、その事実を簡単に複製してしまう可能性があり、その事実の理解が変わると、メンテナンスの問題が発生します。