マジックナンバーとは何ですか?
なぜそれを避けなければならないのですか?
それが適切な場合はありますか?
マジックナンバーとは何ですか?
なぜそれを避けなければならないのですか?
それが適切な場合はありますか?
回答:
マジックナンバーとは、コード内で数値を直接使用することです。
たとえば、(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つのコードの場所を変更する必要があります。私がそれを忘れると、これは矛盾につながります。
JDKにはInteger
、Character
やMath
クラスなどの例がたくさんあります。
PS:FindBugsやPMDなどの静的分析ツールは、コード内のマジックナンバーの使用を検出し、リファクタリングを提案します。
TRUE
/ FALSE
)
マジックナンバーはハードコーディングされた値であり、後の段階で変更される可能性がありますが、更新が困難な場合があります。
たとえば、「ご注文」概要ページに過去50件の注文を表示するページがあるとします。50は、ここではマジックナンバーです。これは、標準や慣習によって設定されたものではないため、仕様で概説されている理由のために作成した数字です。
さて、あなたは何をしているのですか?SQLスクリプト(SELECT TOP 50 * FROM orders
)、あなたのウェブサイト(あなたの最後の50注文)、あなたの注文ログイン(for (i = 0; i < 50; i++)
)、そしておそらく他の多くの場所。
さて、誰かが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つの関数内でのみ定義されたものはすべて受け入れられる可能性がありますが、それはコンテキストに依存します。
SmtpClient.DefaultPort = 25
おそらく明らかである小胞体よりSmtpClient.DefaultPort = DEFAULT_SMTP_PORT
。
25
です。アプリケーション全体を検索し、その発生を25
SMTPポート用にのみ変更する必要があります。たとえば、テーブル列の幅や数値などの25は変更しないでください。ページに表示するレコードの数。
IANA
です。
ウィキペディアのエントリを見てみましたか マジックナンバーのか?
マジックナンバーの参照が行われるすべての方法について少し詳しく説明します。これは、悪いプログラミング慣行としてのマジックナンバーについての引用です
マジックナンバーという用語は、説明なしにソースコードで直接数値を使用するという悪いプログラミング慣行も指します。ほとんどの場合、これによりプログラムの読み取り、理解、維持が困難になります。ほとんどのガイドでは数値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」です。
プログラムのピクセルパディングはシステム全体で「default_padding」の種類であるため、2をさらにふりましょう。したがって、命令を書くpadding = 2
だけでは十分ではありません。「デフォルト」の概念は明らかにされていません。私が書くときだけ:padding = default_padding
文脈として、そして他の場所で:default_padding = 2
私のシステムで2のより良い、より完全な意味(意味論と目的)を完全に実現しますか?
上記の例は、「2」自体は何でもかまわないため、かなり良い例です。理解の範囲とドメインを「マイプログラム」に制限する場合にのみ、「マイプログラム」default_padding
のGUI UX部分の2は「マイプログラム」に限定され、最終的に適切なコンテキストで「2」を理解できるようになります。ここで、「2」は「マジック」番号であり、記号定数に分解されますdefault_padding
、「マイプログラム」のGUI UXのコンテキスト内でdefault_padding
、囲んでいるコードのより大きなコンテキストですぐに理解できるように使用されます。
したがって、その意味(意味および目的)を十分かつ迅速に理解できない基本値は、基本値(マジック番号など)の代わりにシンボリック定数の候補として適しています。
スケール上の数値にもセマンティクスがある場合があります。たとえば、モンスターの概念があるD&Dゲームを作成しているとしましょう。モンスターオブジェクトlife_force
には、整数と呼ばれる機能があります。数字には意味があり、意味を提供する言葉がないとわかりません。したがって、私たちは恣意的に言うことから始めます:
上記の記号定数から、D&Dゲームでのモンスターの生存、死、および「不死」(および起こり得る影響または結果)のメンタルな絵を取得し始めます。これらの単語(記号定数)がなければ、からの範囲の数字だけが残り-10 .. 10
ます。単語のない範囲は、大きな混乱を招く可能性があり、ゲームのさまざまな部分がattack_elves
やのようなさまざまな操作に対するその範囲の意味に依存している場合、ゲームでエラーが発生する可能性がありますseek_magic_healing_potion
。
したがって、「マジックナンバー」の置換を検索して検討するときは、ソフトウェアのコンテキスト内の数値について、また数値が互いに意味的にどのように相互作用するかについてさえ、非常に目的に満ちた質問をしたいと思います。
どのような質問をするべきかを見てみましょう。
あなたが魔法の番号を持っているかもしれません...
コードテキスト内のスタンドアロンのマニフェスト定数の基本値を調べます。そのような値の各インスタンスについてゆっくりと慎重に各質問をしてください。答えの強さを考えてください。多くの場合、答えは白黒ではありませんが、誤解された意味と目的、学習のスピード、理解のスピードの色合いがあります。また、周囲のソフトウェアマシンにどのように接続するかを確認する必要もあります。
結局のところ、交換の答えは、接続を確立するための読者の強さまたは弱さ(たとえば、それを理解すること)の(心の中での)尺度に答えることです。彼らがより早く意味と目的を理解するほど、「魔法」は減ります。
結論:基本値をシンボリック定数で置き換えるのは、マジックが、混乱から生じるバグの検出を困難にするほど十分に大きい場合のみにしてください。
マジックナンバーは、ファイル形式の開始時またはプロトコル交換時の一連の文字です。この番号は、健全性チェックとして機能します。
例:GIFファイルを開くと、最初にGIF89が表示されます。「GIF89」はマジックナンバーです。
他のプログラムは、ファイルの最初の数文字を読み取り、GIFを適切に識別できます。
危険なのは、ランダムなバイナリデータにこれらと同じ文字が含まれる可能性があることです。しかし、それは非常にまれです。
プロトコル交換については、これを使用して、渡されている現在の「メッセージ」が破損しているか無効であることをすばやく識別できます。
マジックナンバーはまだ役に立ちます。
プログラミングでは、「マジックナンバー」はシンボル名を指定する必要がある値ですが、通常は複数の場所でリテラルとしてコードに挿入されています。
同じ理由でSPOT(シングルポイントオブトゥルース)が良いのは良くありません。この定数を後で変更したい場合は、コードを探してすべてのインスタンスを見つける必要があります。また、この数字が何を表しているのかを他のプログラマーには明確にしていない可能性があり、したがって「魔法」であるので、これも悪いことです。
人々は時々、これらの定数を個別のファイルに移動して構成として機能させることにより、マジックナンバーの排除をさらに進めます。これは役立つ場合もありますが、それよりも複雑になる可能性もあります。
(foo[i]+foo[i+1]+foo[i+2]+1)/3
はループよりもはるかに速く評価される場合があります。3
ループとしてコードを書き直さずにを置き換えると、ITEMS_TO_AVERAGE
定義されていると見なされた人は、コード3
を変更し5
て、コードでより多くのアイテムを平均化できると考えます。対照的に、リテラルで式を見た人は、が合計されるアイテムの数を表して3
いることに気付くでしょう3
。
マジックナンバーは、特別なハードコードされたセマンティクスを持つ数値にすることもできます。たとえば、以前に、レコード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をインストールするまで、これを検討したことはありません。
static final
定数を1つの方法で使用する場合、定数はやりすぎだと思います。final
メソッドの先頭で宣言された変数は、より読みやすく私見です。
@ eed3si9n:「1」はマジックナンバーであることをお勧めします。:-)
マジックナンバーに関連する原則は、コードが扱うすべての事実を1回だけ宣言する必要があるということです。コードでマジックナンバーを使用する場合(@marcioが示したパスワードの長さの例など)、その事実を簡単に複製してしまう可能性があり、その事実を理解すると、メンテナンスの問題が発生します。
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
戻り変数はどうですか?
ストアドプロシージャを実装するときに、特に難しいと思います。
次のストアドプロシージャを想像してみてください(例を示すために、構文が間違っています)。
int procGetIdCompanyByName(string companyName);
特定のテーブルに存在する場合、会社のIDを返します。それ以外の場合は、-1を返します。どういうわけかそれは魔法の数です。これまでに読んだいくつかの推奨事項は、私は本当にそのような設計をしなければならないだろうと言っています:
int procGetIdCompanyByName(string companyName, bool existsCompany);
ちなみに、会社が存在しない場合はどうしたらいいのでしょうか?OK:それはexistesCompanyをfalseに設定しますが、-1も返します。
別のオプションは、2つの別々の関数を作成することです。
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
したがって、2番目のストアドプロシージャの前提条件は、会社が存在することです。
しかし、このシステムでは会社が別のユーザーによって作成される可能性があるので、並行性が怖いです。
ちなみに、比較的よく知られていて、何かが失敗した、または何かが存在しないことを伝えるのに安全な「マジックナンバー」を使用することについて、どう思いますか。
マジックナンバーを定数として抽出するもう1つの利点は、ビジネス情報を明確に文書化できる可能性があることです。
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
、今のところ、私の11は人またはビールのボトルなどである可能性があるため、代わりに11を定数に変更します住民など。