コード内のすべての数字は「マジックナンバー」と見なされますか?


21

したがって、引数としてメソッドに送信するコード内のすべての数値は、マジックナンバーと見なされますか?私には、そうすべきではありません。いくつかの数字がユーザー名の最小長であるとしましょう。コードで「6」を使用し始めます...それからメンテナンスの問題があり、ここで「6」は魔法の数字です...引数の1つが、たとえばコレクションのi番目のメンバーとして整数を受け入れるメソッドを呼び出している場合、そのメソッド呼び出しに「0」を渡すと、この場合、「0」が魔法として表示されません数。どう思いますか?


4
あなたの例では、0は何を表していますか?
アーロンカーツハルス

2
あなたが説明する場合、「0」には魔法の特性はまったくありません。
Tulainsコルドバ

4
0,1および42を除くすべてが魔法である
Mawg

回答:


43

数字の意味が文脈で非常に明確な場合、それは「魔法の数字」の問題だとは思わない。

例:文字列の部分文字列を最初からあるトークンまで取得しようとしているとしましょう。コードは次のようになります(想像上の言語とライブラリ)。

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

このコンテキストでは、数値0の意味は十分に明確です。定義START_OF_SUBSTRINGして0に設定できると思いますが、この場合はやり過ぎだと思います(ただし、サブストリングの開始が0でないことがわかっている場合は正しいアプローチですが、それはあなたの状況)。

別の例は、数値が偶数か奇数かを判別しようとしている場合です。書き込み:

isEven := x % 2;

以下ほど奇妙ではありません:

TWO := 2;
isEven := x % TWO;

負の数をテストする

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

また、私には奇妙に感じます、私はむしろ見たいです

isNegativeInt := i <= -1;

6
円の度数を明示的に操作しているコードで別の例を投げるには、360ほとんどの人がそれが何を意味するかを理解していることを理解して完全な回転をマークするなどの数字を使用するのが妥当です定数を指定しても問題ない場合)
-KChaloux

11
KChaloux:できれば、あなたのコメントを-1するでしょう。360は魔法の数字です。360がたまたま別の定数の値である場合、360には2つのセットがあり、それらは無関係で区別できません。Juniorが登場し、「That is a magic number」に進み、グローバル検索を行い、360を「Degrees_in_Circle」に置き換え、すべてのユニットテストと回帰テストを実行し、すべて合格-コード修正を提供します。これでコードは犬の朝食になり、私たちは皆、それが短時間でどうなるかを知っています
....-mattnz

4
@mattnz:この種の大規模なコード変更は、実稼働に入るずっと前に(できればコードレビュー中に、後輩であるなら)すぐにキャッチされることを願っています。そのコンテキストでそれを行う人は、おそらく0私のサブストリングの例のコンテキストでも置き換えると思います。その場合、これは彼らが引き起こす可能性のある損害の最小量かもしれません。幾何学的計算を行うコーディングを行ってから長い時間が経ちましたが、一般的に、値15、30、45、60、90、180、360は受け入れられた定数でした。私は誰が定義した見たことがないFIFTEEN_DEGREES、...
FrustratedWithFormsDesigner

5
@KChaloux DegreesからRadiansへのシフトがある場合、例は実際にばらばらになるかもしれません。360までに、あなたは1つの完全な回転を表現しています。同じ値に対して複数の表現があるので、それを引き出す必要があります。特に、360PIを考えると2PI(180回転ですが、最終的には同じ方向を指します)、または360回転は1回転と同じですが、副作用が異なる場合があります。
クリス

14
そこにはちょっとしたストローマン、TWOとMINUS_ONEはまったく悪いです。なぜなら、マジックナンバーをテキストでのレンダリングに置き換えるのは当然のことです。定数の名前はその意味を伝える必要があります。あなたの例は、数字に関する基本的な事実に関するものであり、それらの特定の数字だけに密接に結びついていることを除いて、それ以上の意味は本当にありません。
マイケルボルグワード

17
bool hasApples = apples > 0;

ゼロは不在を意味することは明らかです。0は、「absenceValue」という名前の変数よりも理解しやすいと思います。


for(int i=0; i < arr.length; i++)

0が開始位置であることは明らかです。「firstPosition」という名前の変数に戸惑うでしょう。このような変数は、開始位置が変更される可能性があるのではないかと思います。


14

何かが定数宣言であるべきかどうかを決定する際に、3つの重要な要素を提案します。

  1. 数字は正確かつ簡潔に表現できるものですか
  2. 値を変更する必要があるが、コードを書き換える必要のないもっともらしいシナリオはありますか
  3. 番号を見た人は、名前付き定数を見た人よりも早くまたは遅く認識しやすいでしょうか?

数値リテラルは不必要に冗長、不必要に不正確、またはその両方になりやすいため、piのようなものはおそらく数値リテラルとしてではなく、名前付き定数として記述する必要があります。キャッシュ内のスロット数のようなものはおそらく名前付き定数である必要があります(ただし、以下の注を参照)。if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;式の方がほぼ確実に読みやすいので、ステートメント内の数字「4」、「28」、「29」のようなものはおそらく定数と名付けるべきではありませんif ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;。標準の管理者は、その年の2100年2月の長さが上記の式と一致しないことを示していることに注意してください。 そのような日付を正しく処理するための障害(つまり、コードが整数オーバーフローまたは他のそのような問題によってトリップすることはありません)。

ルール#2の重要な注意点は、コードが名前付き定数で簡単に表現できない方法でハードコードされた数値に依存する場合があることです。たとえば、離散パラメーターとして渡される2つのベクトルの外積を計算する方法は、3次元ベクトルで使用する場合にのみ意味があります。必要な次元数は、ルーチンを完全に書き直さずに有意に変更できる値ではありません。3つの4次元ベクトルの外積を計算する必要性を予見したとしても、値 "3"に名前付き定数を使用しても、その必要性を満たせるようになることはほとんどありません。


4

これは、すべての原則として、程度の問題です。一般的に、ソースコードの数値リテラルは、サイズが大きいほど疑わしいです。10のような最大長または0x587FB0のようなメモリアドレスは明らかに悪い習慣です-遅かれ早かれこれらの値を複数回繰り返さなければならないことはほぼ確実です。かわった。

0はスケールのもう一方の端にあります。まだ疑わしいが、それほど多くはない。センチネル値として0を使用していますか?次に、おそらく定数がその意味を説明できるという理由だけで、代わりにシンボリック定数を使用する必要があります。「0は成功を意味する」など、非常に定着した文化的合意ですか?おそらく大丈夫です。「コレクションの最初のアイテム」という意味ですか?それは無害かもしれませんが、first()私のような代替方法があるなら、おそらくそれを好むでしょう。


1
「センチネル値として0を使用していますか?」<-「センチネル」の意味をここで説明できますか?一致していると思われる定義が見つかりません。
-rory.ap

3

コンテキストからすぐに明らかにならない名前のない数字はすべてマジックナンバーです。コンテキストからすぐに明らかになる意味を持つ数字を定義するのは少しばかげています。

django(python web framework)では、次のような生の番号でデータベースフィールドを定義できます。

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

言うよりも明確です(そして推奨されるプラクティス

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

長さを変更する必要はほとんどないので(そして常にmax_lengthフィールドの長さと比較できます)。アプリケーションを最初にデプロイした後にフィールドの長さを変更する必要がある場合、djangoコードのフィールドごとに正確に1つの場所で変更し、さらにDBのスキーマを変更するための移行を記述する必要があります。max_lengthオブジェクトのタイプの定義済みフィールドを参照する必要がある場合、直接行うことができます-それらのフィールドがPersonクラスを定義している場合Person._meta.get_field('firstname').max_lengthmax_length使用されている(1つの場所で定義されている)。同じ40が複数のフィールドに使用されたという事実は、それらを個別に変更する場合があるため、無関係です。firstnameの長さは、middlenameまたはlastnameの長さに決して依存しないでください。これらは個別の値であり、独立して変更できます。

多くの場合、配列インデックスは名前のない数字を使用できます。python辞書に入れたいデータのCSVファイルがあり、行の最初の要素を辞書としてkey記述した場合のように:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

もちろん、名前index_column = 0を付けて次のようなことをすることができます。

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

またはさらに悪いことに、after_index_col = index_col + 1を取り除くように定義しますindex_col+1が、それは私の見解ではコードを明確にしません。また、index_col名前を付けると、列が0でない場合でもコードが機能するようになります(そのためrow[:index_col] +)。


7
実際には、max_lngth=40対がmax_length=MAX_LENGTH_NAMEある古典的なマジックナンバーのexamlple 悲鳴象徴します。45文字の名前をサポートしたい日が来ますが、今では「40」の使用はすべて疑わしく、慎重に調べる必要があります。
ロスパターソン

1
@RossPatterson-これは、グローバル変数MAX_ARRAY_SIZEと常に比較するCではなく、適切なWebフレームワークです。マジックナンバーが現れる唯一の場所は、データベースモデルを宣言する場所です。他のすべてはこの値と比較されます(たとえば、40はコードのどこにも表示されません)。また、DBに関連付けられているため、スキーマの移行を行わずにこの変数を簡単に変更することはできません。私は、コードの変化にその直後に明白な1位1つの文字ミドルネームを言うために変更したい場合401。コンテキストを考える必要があります。
ジンボブ博士13年

2
申し訳ありませんが、2つの点で間違っています。まず、OPは言語を指定しない「プログラミングプラクティス」の質問をしました。彼らは「メソッド」ではなく「メソッド」と言ったので、オブジェクト指向の何かを想定しましょう。しかし、それはマジックナンバーのデータの領域から私たちを連れ去りません。マジック番号がデータベース(に焼成される場合には、第2は、例えば、スキーマ)、それはコードでそれを持っているさらに悪いです。正しいことは、そのソースからほぼ一定のマジックを取得することです。データベース自体、またはこれらのすべての定数を一元化するスキーマモジュールのいずれかです。
ロスパターソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.