クリーンコード-リテラル1を定数に変更する必要がありますか?


14

マジックナンバーを避けるために、リテラルに意味のある名前を付ける必要があるとよく耳にします。といった:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

私はこのようなダミーメソッドを持っています:

説明:提供する人のリストがあり、デフォルトでは食べ物だけを買うために5ドルを費やしていると思いますが、複数の人がいる場合、水と食べ物を買う必要があります。コードを変更します。リテラル1に注目してください。それについての質問です。

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

友達にコードをレビューするように頼んだとき、1人は値1に名前を付けるときれいなコードが得られると言い、もう1人は値が意味があるのでここに定数名は必要ないと言った。

したがって、私の質問は、リテラル値1に名前を付ける必要がありますか?値はいつ魔法の数であり、そうでないのですか?コンテキストを区別して最適なソリューションを選択するにはどうすればよいですか?


どこpersonsから来て、何を説明していますか?コードにはコメントがまったくないため、コードの実行内容を推測するのは困難です。
ヒューバート・グジェスコヴィアック

7
それは1よりも明確にはなりません。ただし、「サイズ」を「カ​​ウント」に変更します。そしてmoneyServiceはそのままバカです。個人のコレクションに応じて、どれだけのお金を返すかを決定できるはずです。したがって、getMoneyに人を渡し、例外的なケースを整理します。
マーティンマート

4
if句から新しいメソッドに論理式を抽出することもできます。おそらくIsSinglePerson()と呼びます。そうすれば、その1つの変数だけでなく、if句をもう少し読みやすくすることができます
...-selmaohneh


2
おそらく、このロジックはmoneyService.getMoney()により適しているでしょうか?1人の場合にgetMoneyを呼び出す必要がある瞬間がありますか?しかし、私は1が明らかであるという一般的な感情に同意します。魔法の数字は、あなたの頭は、プログラマはその数に到達したか尋ねるスクラッチする必要があると思い数字です。..すなわちif(getErrorCode().equals(4095)) ...
ニール・

回答:


23

いいえ。その例では、1は完全に意味があります。

ただし、persons.size()がゼロの場合はどうなりますか?persons.getMoney()0と2で機能するが1では機能しない奇妙なようです。


1には意味があることに同意します。おかげで、ところで、私は私の質問を更新しました。あなたは私のアイデアを得るために再びそれを見ることができます。
ジャック

3
私が長年にわたって何度も聞いてきたフレーズは、マジックナンバーは0と1以外の名前のない定数で あるということです。時間。
Baldrickk

@Baldrickk時には、他の数値リテラルも名前の後ろに隠してはいけません。
デュプリケータ

@Deduplicatorの例が思い浮かびますか?
Baldrickk


18

なぜコードにはその特定のリテラル値が含まれているのですか?

  • この値は問題のあるドメインで特別な意味を持ちますか?
  • または、この値は単なる実装の詳細であり、その値は周囲のコードの直接的な結果ですか?

リテラル値がコンテキストから明確でない意味を持っている場合、はい、その値に定数または変数を介して名前を付けると役立ちます。後で、元のコンテキストが忘れられると、意味のある変数名を持つコードの保守性が高まります。コードの対象者は、主にコンパイラーではありません(コンパイラーは恐ろしいコードを喜んで処理します)が、そのコードの将来のメンテナーです。

  • あなたの最初の例では、などのリテラルの意味は3445文脈から明らかではありません。代わりに、これらの値の一部は、問題の領域で特別な意味を持ちます。したがって、彼らに名前を付けるのは良かったです。

  • 2番目の例では、リテラルの意味は1コンテキストから非常に明確です。名前の紹介は役に立ちません。

実際、明らかな値に名前を付けると、実際の値が隠されるため、悪い場合もあります。

  • これにより、特に同じ変数が関連のないコード部分で再利用される場合、名前付きの値が変更されたり間違っていたりすると、バグがわかりにくくなる可能性があります。

  • コードの一部は特定の値に対しては正常に機能する場合もありますが、一般的な場合は正しくない場合があります。不要な抽象化を導入することにより、コードは明らかに正しくなくなります。

「明白な」リテラルにはサイズの制限はありません。これはコンテキストに完全に依存するためです。たとえば、リテラル1024は、ファイルサイズの計算のコンテキストでは完全に明白で31あり、ハッシュ関数のコンテキストではリテラルpadding: 0.5emであり、CSSスタイルシートのコンテキストではリテラルです。


8

このコードにはいくつかの問題がありますが、これは次のように短縮できます。

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. 1人が特別なケースである理由は不明です。ある人からお金を得るのは、複数の人からお金を得るのとは根本的に異なるという特定のビジネスルールがあると思います。しかし、私は、との両方を調べて、なぜ個別のケースがあるのか​​を理解したいgetMoneyIfHasOnePersongetMoney考えています。

  2. 名前getMoneyIfHasOnePersonが正しくありません。名前から、このメソッドが1人の人物がいるかどうかを確認し、そうであれば、彼からお金を得ることが期待されます。それ以外の場合は、何もしません。あなたのコードから、これは起こっていることではありません(または、条件を2回実行しています)。

  3. List<Money>コレクションではなくを返す理由はありますか?

戻るあなたの質問に、ので、一人のための特別な治療法があり、なぜそれが不明である、数字1は、定数に置き換える必要がある場合を除き、ルールを明示的にする別の方法があります。ここで、1つは他のマジックナンバーと大差ありません。特別な待遇は1人、2人、3人、または12人以上にのみ適用されるというビジネスルールがあります。

コンテキストを区別して最適なソリューションを選択するにはどうすればよいですか?

コードをより明確にするために何でもします。

例1

次のコードを想像してください。

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

ここでゼロは魔法の値ですか?コードはかなり明確です。シーケンスに要素がない場合は、処理せずに特別な値を返しましょう。ただし、このコードは次のように書き換えることもできます。

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

ここでは、これ以上定数はなく、コードはさらに明確です。

例2

別のコードを取得します。

const result = Math.round(input * 1000) / 1000;

JavaScriptなどのround(value, precision)オーバーロードのない言語で何をするのかを理解するのに時間がかかりません。

さて、定数を導入したい場合、どのように呼び出されますか?取得できる最も近い用語はPrecisionです。そう:

const precision = 1000;
const result = Math.round(input * precision) / precision;

可読性が向上しますか?それは可能性があります。ここで、定数の値はかなり制限されており、リファクタリングを本当に実行する必要があるかどうかを自問することができます。ここでの良い点は、精度が1回だけ宣言されるようになったことです。したがって、変更しても、次のような間違いを犯すリスクはありません。

const result = Math.round(input * 100) / 1000;

1つの場所で値を変更し、他の場所でそれを行うのを忘れます。

例3

これらの例から、数字はすべての場合に定数に置き換えられるべきであるという印象を持つかもしれません。本当じゃない。状況によっては、定数を使用してもコードが改善されないことがあります。

次のコードを取得します。

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

ゼロを変数で置き換えようとすると、意味のある名前を見つけることが困難になります。どう名前をつけますか?ZeroPositionBaseDefault?ここで定数を導入しても、コードはまったく強化されません。それはわずかに長くなり、それだけになります。

ただし、そのようなケースはまれです。そのため、コードで数字を見つけたときはいつでも、コードをリファクタリングする方法を見つけようと努力してください。番号にビジネス上の意味があるかどうかを自問してください。はいの場合、定数は必須です。いいえの場合、どのように番号を付けますか?意味のある名前を見つけたら、それは素晴らしいことです。そうでない場合は、定数が不要な場合があります。


3aを追加します。変数名、使用量、残高などにmoneyという単語を使用しないでください。お金のコレクションは意味をなさず、金額または残高のコレクションは意味をなします。(OK私は外国のお金(またはむしろコイン)の小さなコレクションを持っていますが、それは別の非プログラミング問題です)。
ベント

3

単一のパラメーターを取り、4を5で割った値を返す関数を作成することで、最初の例を使用しながら、その機能に明確なエイリアスを提供できます。

私は自分の通常の戦略を提供しているだけですが、多分何かを学ぶでしょう。

私が考えているのは。

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

私がベースを離れていても申し訳ありませんが、私はただのJavaScript開発者です。私がこれを正当化した構成がわからない。すべてが配列またはリストにある必要はないが、.lengthが多くの意味をなすと思う。そして* 4は、その起源が曖昧であるため、良い定数になります。


5
しかし、これらの値4と5はどういう意味ですか?それらのいずれかを変更する必要がある場合、同様のコンテキストで番号が使用されているすべての場所を検索し、それに応じてそれらの場所を更新できますか?それが元の質問の核心です。
バートヴァンインゲンシェ

最初の例は自明でしたので、私が答えられるものではないかもしれません。必要なことを達成するために新しい操作を作成する必要がある場合、操作は将来変更されません。コメントは、この目的に最も役立つと思います。私の言語で減らすための1行の呼び出し。素人が完全に理解できるようにすることはどれほど重要ですか?
-etisdew

@BartvanIngenSchenau関数全体の名前が不適切です。私は、より良い関数名、関数が返すはずの仕様を含むコメント、および* 4/5がそれを達成する理由のコメントを好むでしょう。定数名は実際には必要ありません。
gnasher729

@ gnasher729:名前が適切に文書化された関数のソースコードに定数が1回だけ現れる場合、名前付き定数を使用する必要はないかもしれません。定数が同じ意味で複数回現れるとすぐに、その定数に名前を付けると、リテラル42のすべてのインスタンスが同じものを意味するかどうかを把握する必要がなくなります。
バートヴァンインゲンシェ

その中心で、期待する場合は、値yesを変更する必要があるかもしれません。ただし、変数を表すためにconstに変更します。私はそうではないと思いますが、これは単にその上のスコープに存在する中間変数であるべきです。私はすべてを関数のパラメーターにしたいとは言いませんが、それが変更できる場合でも、その変更を行うために現在作業している関数またはオブジェクト/構造体スコープの内部パラメーターにすることはめったにありませんグローバルスコープのみ。
etisdew

2

その番号1、別の番号でしたか?2、3、または1でなければならない論理的な理由はありますか?1でなければならない場合は、1を使用しても問題ありません。それ以外の場合は、定数を定義できます。その定数ONEを呼び出さないでください。(私はそれを見ました)。

1分で60秒-定数が必要ですか?まあ、それ 50秒や70秒ではなく、60秒です。そして誰もがそれを知っています。だから、それは数字を維持することができます。

1ページあたり60個のアイテムが印刷されます。その数は簡単に59または55または70だったはずです。実際、フォントサイズを変更すると、55または70になる可能性があります。

また、その意味がいかに明確であるかも問題です。「分=秒/ 60」と書くと、それは明らかです。「x = y / 60」と書くと、それは明らかではありません。どこかに意味のある名前がなければなりません。

絶対ルールが1つあります。絶対ルールはありません。練習すれば、いつ数字を使用するか、いつ名前付き定数を使用するかがわかります。本がそう言うので、それをしないでください-それがなぜそれを言うかを理解するまで。


「1分で60秒...そして誰もがそれを知っています。それで数字を維持できます。」-私にとって有効な議論ではありません。「60」が使用されているコードに200の場所がある場合はどうなりますか?秒単位から分単位で使用されている場所と、他のおそらく「自然な」使用方法を見つけるにはどうすればよいですか?-しかし、実際には、私も、追加の定数を宣言せずに必要なminutes*60場合でもhours*3600、使用することを敢えてしています。おそらく、私は書くd*24*3600か、誰かが一目でそのマジックナンバーを認識しない端に近いd*24*60*60ので、書き86400ます。
JimmyB

@JimmyBコードの200箇所で「60」を使用している場合、ソリューションは定数を導入するのではなく、関数をリファクタリングする可能性が非常に高くなります。
klutt

0

DB取得の(変更された)OPのようなかなりの量のコードを見てきました。クエリはリストを返しますが、ビジネスルールでは要素は1つしか存在できないとされています。そして、もちろん、「この1つのケース」のために、複数の項目を持つリストに何かが変更されました。(ええ、私はかなりの回数言った。それはほとんど彼らのようだ... nm)

したがって、定数を作成するのではなく、(クリーンコードメソッドで)名前を指定するメソッドを作成するか、条件の検出対象を明確にします(検出方法をカプセル化します)。

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}

取り扱いを統一するためにはるかに好ましい。n個の要素のリストは、nが何であっても一様に処理できます。
デデュプリケーター

私が見たところ、そうではありませんでしたが、実際には、単一のケースは、同じユーザーがnサイズのリストに含まれていた場合とは異なる(限界)値でした。よくできたオブジェクト指向では、これは問題にならないかもしれませんが、レガシーシステムを扱う場合、適切なオブジェクト指向実装が常に実行可能であるとは限りません。私たちが得た最高のものは、DAOロジックの合理的なオブジェクト指向ファサードでした。
クリスチャンH
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.