定数を使用するのは正しい状況ですか?


42

それで、私の教授は私が取り組んでいるプロジェクトについてフィードバックをしてくれました。彼はこのコードにいくつかのマークを付けました。

if (comboVendor.SelectedIndex == 0) {
  createVendor cv = new createVendor();
  cv.ShowDialog();
  loadVendors();
}

これは、コンボボックスの「インデックス変更」ハンドラーにあります。ユーザーが新しいベンダーを作成したい場合に使用されます。私の一番上のオプション(インデックス0、変更されることはありません)は[新しいベンダーの作成]ダイアログを開きます。したがって、コンボボックスの内容は次のようになります。

Create New Vendor...
Existing Vendor
Existing Vendor 2
Existing Vendor 3

彼の問題は最初の行コードにあります:

if (comboVendor.SelectedIndex == 0)

彼は、0は定数であるべきだと主張し、そのために実際にマークをドッキングしました。彼は、コードでリテラルを使用すべきではないと主張しています。

問題は、なぜその状況でそのコードを一定にしたいのか理解できないことです。そのインデックスは決して変化せず、微調整する必要があるものでもありません。非常に特定の状況で使用され、変更されない単一の0をメモリに保持することは、メモリの無駄のようです。


32
彼は独断的です。一般に、マジックナンバーは避けるべきです。-1、0、1はそのルールの例外と見なすことができると思います。また、このような定数は、リテラル0より任意のより多くの部屋を取ることはないだろう
デイヴ・ムーニー

23
@DaveMooney:ここでひざまずい反応をしていないのですか?それはのようなものというのは本当だ-1str.indexOf(substr) != -1「のためstr含まれていsubstrprefectly正当化されます」。しかし、ここでは、0の意味は明らかではありません(新しいベンダーを作成することとの関係はどうですか?)、または真に一定ではありません(新しいベンダーを作成する方法が変わったらどうなりますか?)。

62
あなたはルールを破ることを得る前に、ルールを学ばなければならない
Ryathal

12
私はこの方法を思いがけず失敗させました。リストはアルファベット順にソートされました。使用--新規作成--ダッシュが最初にソートされ、ハードコードされたインデックス0がCreateNewメソッドにソートされます。次に、誰かがダッシュの前にソートされる単一の引用「マイアイテム」で始まるアイテムを追加しました。私のハードコーディングにより、次回リストがロードされたときにプログラムがクラッシュしました。リストのデータファイルを手動で変更して、顧客のデータを回復する必要がありました。
Hand-E-Food

14
int.Zero代わりに使用して、彼を幸せにすることができます:)
ポールストーベル

回答:


90

C#でこれを行う実際の正しい方法は、ComboItemの順序まったく依存しないことです。

public partial class MyForm : Form
{
    private readonly object VENDOR_NEW = new object();

    public MyForm()
    {
        InitializeComponents();
        comboVendor.Items.Insert(0, VENDOR_NEW);
    }

    private void comboVendor_Format(object sender, ListControlConvertEventArgs e)
    {
        e.Value = (e.ListItem == VENDOR_NEW ? "Create New Vendor" : e.ListItem);
    }

    private void comboVendor_SelectedIndexChanged(object sender, EventArgs e)
    {
        if(comboVendor.SelectedItem == VENDOR_NEW)
        {
            //Special logic for selecting "create new vendor"
        }
        else
        {
            //Usual logic
        }
    }
}

22
これがC#の場合、定数にALL_CAPSを使用しないでください。定数はPascalCasedしなければならない- stackoverflow.com/questions/242534/...
Groky

4
@Groky:なぜこれが重要なのですか?誰が彼の定数に名前を付けるのを気にしますか?定数に一貫した方法でALL_CAPSを使用する場合、これは100%正しいです。
marco-fiset

4
@marcof:まあ、定数がパブリックインターフェイスの一部である場合、MS命名ガイドラインに従う必要があります。そうでない場合、彼は少なくともベストプラクティスを早期に学習する必要があります。
Groky

2
これはまだ間違っています。問題を整数リテラル(ゼロ)から文字列リテラル(新規ベンダーの作成)にシフトします。せいぜい問題は、「インデックスが変更された場合」ではなく「ラベルが変更された場合」です。
フライハイト

7
@Freiheit:間違っています。ここでの文字列定数は表示専用です。プログラムのロジックにはまったく影響しません。プログラム状態を保存するためにマジック値/文字列を使用するのとは異なり、この文字列を変更する(またはリストに項目を追加/削除する)と、何も壊れません。
BlueRaja-ダニーPflughoeft

83

コンボボックスの順序は変更される場合があります。「新規ベンダーの作成...」の前に「特別ベンダーの作成...」などの別のオプションを追加するとどうなりますか

定数を使用する利点は、コンボボックスの順序に依存するメソッドが多数ある場合、定数を変更するだけで、すべてのメソッドを変更する必要がある場合です。

定数を使用することは、リテラルよりも読みやすくなります。

if (comboVendor.SelectedIndex == NewVendorIndex)

ほとんどのコンパイル済み言語は、コンパイル時に定数を置き換えるため、パフォーマンスが低下することはありません。


5
変更可能なインデックスに依存するのではなく、value属性で説明的なコードを使用します(作成オプションをリストの一番下に移動すると、定数メソッドが完全に壊れます)。次に、そのコードを探します。
CaffGeek

1
@Chad GUIの順序でコントロールを識別することは非常に脆弱です。言語が、ルックアップ可能なgui要素への値の追加をサポートしている場合、それを使用します。
マイケルクルセル

3
@solutionが慣例だからです。
イッケ

3
@Ikkeそれは間違いなく慣習ではありません。stackoverflow.com/questions/242534/...
SolutionYogi

1
C#について話している場合、NewVendorIndexが慣例です。他の.NETスタイルと一貫性があります。
MaR

36

あなたが説明するこの状況は判断の呼び出しであり、個人的には一度しか使用されておらず、既に読み取り可能な場合は使用しません。

本当の答えは、彼がレッスンを教えるためにこれを選んだということです。

彼が教授であることを忘れないでください、彼の仕事はあなたにコーディングとベストプラクティスを教えることです。

彼は実際にかなり良い仕事をしていると思います。

確かに彼は少し絶対に外れるかもしれませんが、マジックナンバーを使う前にあなたがもう一度考えると確信しています。

また、この状況でベストプラクティスと見なされるものを見つけるために、プログラマーに関するオンラインコミュニティに参加するのに十分なほど、彼はあなたの皮の下に入りました。

教授に敬意を表します。


この場合、手段が最終結果を正当化することを示すために+1
oliver-clare

@ Thanos-「彼が教授であることを忘れないでください。彼の仕事はあなたにコーディングとベストプラクティスを教えることです。」これは私の教授には決して当てはまりませんでした。彼の仕事は、コースが作成された後、部門が重要だと感じることをあなたに教えることだと思います。
ラムハウンド

13

[...]一番上のオプション(インデックス0、変更されない)は、[新しいベンダーの作成]ダイアログを開きます。

あなたがそれを説明しなければならなかったという事実は、なぜ定数を使うべきかを証明しています。のような定数を導入した場合NEW_VENDOR_DIALOG、コードはよりわかりやすくなります。また、コンパイラーは定数を最適化するため、パフォーマンスに変化はありません。

コンパイラーではなく、プログラマー向けのプログラムを作成してください。特に最適化を試みている場合を除き、それはあなたのようには見えません。


2
パフォーマンスに言及する場合でも-1。最適化されていない場合でも、ユーザーが気付く前に、コンピューターはこのような数十億の操作を実行できます。
ボリスヤンコフ

3
@Boris OPはリソースの無駄遣いを心配しているようでした。そのため、私の答えがいかに正しくないのかわかりません。
KBA

12

彼は、0は定数であるべきだと主張し、そのために実際にマークをドッキングしました。

同意します。ここでのゼロの使用は「魔法」です。このコードを初めて読んでいると想像してください。ゼロが特別な理由はわかりませんが、リテラルはゼロが特別な理由については何も伝えません。代わりにあなたが言ったならif(comboVendor.SelectedIndex == CreateNewVendorIndex)、コードが何を意味するのかを初めて読む読者にとって非常に明確になるでしょう。

彼は、コードでリテラルを使用すべきではないと主張しています。

それは極端な位置です。現実的な位置は、リテラルの使用は、コードが可能な限り明確でないかもしれないことを示す赤い旗であると言うことです。適切な場合もあります。

問題は、なぜそのような状況でそのコードを一定にしたいのか理解できないことです。そのインデックスは変更されません

それが決して変わらないということは、それを一定にする素晴らしい理由です。そのため、定数は定数と呼ばれます。変わらないからです

また、微調整する必要があるものでもありません。

本当に?誰かがコンボボックス内の物事の順序を変更したいと思うような状況を見ることはできませんか?

これが将来変更される可能性がある理由がわかるという事実は、それを一定にしない良い理由です。むしろ、定数でない読み取り専用の静的整数フィールドである必要があります。定数は、常に同じであることが保証されている量でなければなりません。Piと金の原子番号は良い定数です。バージョン番号はありません。それらはすべてのバージョンを変更します。金の価格は明らかにひどい定数です。それは毎秒変化します。決して変わらない不変のものを作るだけです。

非常に特定の状況で使用され、変更されない単一の0をメモリに保持することは、メモリの無駄のようです。

今、私たちは問題の核心に来ます。

これはおそらく、(1)メモリ、および(2)最適化に関する深い欠陥のある理解があることを示しているため、質問で最も重要な行です。あなたは学校で学び、今は基本を正しく理解する絶好の機会です。「メモリに単一のゼロを保持することはメモリの無駄だ」と考える理由を詳しく説明できますか?まず、少なくとも20億バイトのユーザーがアドレス指定可能なストレージを備えたプロセスで4バイトのメモリの使用を最適化することが重要だと考えるのなぜですか? 第二に、ここで消費されているリソースを正確に想像してください。「メモリ」が消費されるとはどういう意味ですか?

これらの質問への回答に興味があります。最初は、最適化とメモリ管理の理解がいかに間違っているかを学ぶ機会であるためです。正しい信念を持つように導くより良いツール。


6

彼は正しい。あなたが正しい。あなたが間違っている。

彼は、概念的には、マジックナンバーを避けるべきだと考えています。定数は、数値の意味にコンテキストを追加することでコードを読みやすくします。将来、誰かがあなたのコードを読むとき、彼らは特定の番号が使用された理由を知っています。また、値を行のどこかで変更する必要がある場合は、特定の番号が使用されているすべての場所を探し出すよりも、1か所で値を変更する方がはるかに優れています。

そうは言っても、あなたは正しい。この特定のケースでは、定数が保証されるとは本当に思わない。リストの最初のアイテムを探していますが、これは常にゼロです。23になることはありません。または-piです。特にゼロを探しています。定数にすることでコードを乱雑にする必要はないと思います。

ただし、定数を変数として使用することを想定して、「メモリを使用する」ことは間違っています。人間とコンパイラーには定数があります。コンパイラーに、コンパイル中にその場所にその値を入れるように指示します。そうしないと、それ以外の場合はリテラル数を入れていました。そして、たとえ最も要求の厳しいアプリケーションを除いて、メモリ内に定数を保持していても、効率の低下は測定することすらできません。単一の整数のメモリ使用を心配することは、間違いなく「時期尚早な最適化」に該当します。


1
3番目の段落がそうでない場合、リテラル0を使用することは十分に明確であり、定数は保証されないと主張して、この回答にほぼ投票していました。はるかに優れた解決策は、コンボボックスアイテムのインデックス付けにまったく依存せず、代わりに選択されたアイテムのに依存することでした。マジック定数は、0または3.14またはその他であってもマジック定数です。コードを読みやすくするため、適切な名前を付けてください。
ローランドテップ

リストの値を使用する方が良いかもしれませんが、それは彼の質問が何であるかではありませんでした。彼の質問は、それが定数の適切な使用であるかどうかについてでした。また、GUIとのインターフェースのコンテキスト内で0と比較する場合(何らかの理由で-誰かが値に関係なく最初のアイテムを探している可能性があります)、その場所で定数を使用する必要はないと思います。
GrandmasterB

私は同意しません...コードを見るだけでは、0の重要性がすぐに明らかになることは決してありません。比較が代わりに 'comboVendor.SelectedIndex == FirstIndex'である場合、はるかに読みやすくなりますか?
ローランドテップ

2

0ように、意味を明確にするために定数に置き換えNewVendorIndexます。注文が変わるかどうかはわかりません。


1

それがあなたの教授の好みです。通常、定数を使用するのは、リテラルが複数回使用される場合、行の目的を読者に明らかにしたい場合、または将来的にリテラルが変更される可能性があり、変更する場合のみ一箇所で。ただし、今学期は教授が上司なので、そのクラスでこれからやります。

企業の世界のための良いトレーニング?おそらく。


2
「リテラルが複数回使用される場合にのみ定数を使用する」という部分には同意しません。0(場合によっては-1、1)を使用すると、多くの場合、明確になります。ほとんどの場合、コードを読むほどわかりやすいものに名前を付けるとよいでしょう。
ヨハネス

1

正直に言うと、あなたのコードはベストプラクティスではないと思いますが、彼の提案は率直に言って少しグロテスクです。

.NETコンボボックスのより一般的なプラクティスは、「Select ..」アイテムに空の値を与え、実際のアイテムには意味のある値を設定してから、次のようにすることです。

if (string.IsNullOrEmpty(comboVendor.SelectedValue))

のではなく

if (comboVendor.SelectedIndex == 0)

3
あなたの例では、nullは単なる別のリテラルです。このレッスンの中心は、リテラルを避けることです。
11

@overslacked-私の例にはヌルリテラルはありません。
Carson63000

nullリテラルがあったとしても、nullは使用可能なリテラルです。オブジェクトへの参照がnullであるかどうかをチェックせずに、その参照がnullであるかどうかをチェックする方法はないと思います。
ラムハウンド

Carson63000-IsNullOrEmptyの使用について言及していました。ある「魔法の価値」を別の「魔法の価値」と交換しています。@Ramhound-ヌルは場合によっては受け入れられるリテラルであり、疑問の余地はありませんが、これが良い例だとは思いません(マジック値としてヌルまたはブランクを使用)。
11

「少しグロテスクな」場合は-1。値を使用する方がより一般的で理解しやすいことは間違いありませんが、selectedindexを使用する場合、名前付き定数を使用することは0よりも確実に優れています。
jmoreno11年

1

彼は、定数を使用することの価値を強調することは間違っていませんし、リテラルを使用することも間違っていません。これが予想されるコーディングスタイルであると強調していない限り、リテラルは有害ではないため、リテラルを使用することでマークを失うことはありません。商用コードで何度もリテラルが使用されているのを見てきました。

彼のポイントは良いです。これは、定数の利点をあなたに知らせる彼の方法かもしれません:

1-偶発的な改ざんからコードをある程度保護します

2- @ DeadMGが答えで言っているように、同じリテラル値が多くの場所で使用されている場合、誤って異なる値で表示される可能性があるため、定数は一貫性を保持します。

3定数は型を保持するため、0Fのような0Fのようなものを使用する必要はありません。

4-読みやすくするために、COBOLは値ゼロの予約語としてZEROを使用します(ただし、リテラルゼロを使用することもできます)-したがって、値に名前を付けると役立つ場合があります。たとえば、(出典:ms-Constants

class CalendarCalc
{
    const int months = 12;
    const int weeks = 52; //This is not the best way to initialize weeks see comment
    const int days = 365;

    const double daysPerWeek = (double) days / (double) weeks;
    const double daysPerMonth = (double) days / (double) months;
}

またはあなたの場合のように(@Michael Krusselの回答に示されているように)


2
それは週あたりの日数の奇妙な定義になりませんか?7.4287143ではなく、週7日があります
ウィンストン

@WinstonEwert、コメントありがとうございます。365/52 = 7.01923076923077 = 7 +(1/52)。ここで、小数部分を削除して7 * 52を計算すると、364日になりますが、これは1年の正しい日数ではありません。分数を持つことは、それを失うことよりも正確です(数値を表示するだけの場合は、結果を7に表示するようにフォーマットできるため)。とにかく、それは定数に関するMSからの単なる例でしたが、あなたのポイントは興味深いです。
NoChance

1
もちろん、その例にすぎませんが、分数を含める方が正確であるという声明に反対します。1週間は7日間と定義されています。あなたの定義を考えると、26週間は182.5日であり、正確ではありません。本当に、問題はあなたですint weeks = 52、1年に52週がありません。1年には52.142857142857146週間があり、これが端数を保持する必要のある数です。もちろん、その定数セット全体で実際に一定であるのは月数だけです。
ウィンストンイーバート

0

複雑な派生がある場合、または頻繁に繰り返される場合にのみ、定数に入れる必要があります。それ以外の場合、リテラルは問題ありません。定数にすべてを入れるのは完全にやり過ぎです。


2回頻繁に検討し、コードを変更する必要がない場合のみ。2つのケースのいずれかを変更することで、バグを作成するのは本当に簡単です。
BillThor

0

実際、前述のように、位置が変わったらどうなりますか?あなたができる/すべきことは、インデックスに依存するのではなく、コードを使用することです。

したがって、選択リストを作成すると、次のようなhtmlになります

<select>
    <option value='CREATE'>Create New Vendor...</option>
    <option value='1'>Existing Vendor</option>
    <option value='2'>Existing Vendor 2</option>
    <option value='3'>Existing Vendor 3</option>
</select>

次に、をチェックする代わりにselectedIndex === 0、値がCREATECODE定数であり、このテストと選択リストの作成の両方で使用されていることを確認します。


3
おそらく良いアプローチですが、C#を使用しているので、htmlは最も期待できるサンプルコードではありません。
ウィンストン・イーバート

0

私はそれを完全に取り除きます。コンボボックスリストの横に新しいボタンを作成するだけです。リスト内のアイテムをダブルクリックして編集するか、ボタンをクリックします。コンボボックスに新しい機能を埋めないでください。次に、マジックナンバーが完全に削除されます。

一般に、コード内のリテラル数値は、数値の前後にコンテキストを置くために定数として定義する必要があります。ゼロとはどういう意味ですか?この場合、0 = NEW_VENDOR。それ以外の場合、それは何か異なることを意味するかもしれません。そのため、読みやすさと保守性のために、コンテキストを前後に配置することは常に良い考えです。


0

他の人が言ったように、インデックス番号以外の方法を使用して、特定のアクションに対応するコンボボックス項目を識別する必要があります。または、プログラムロジックを使用してインデックスを検索し、それを変数に格納できます。

私が書いている理由は、「メモリの使用」に関するあなたのコメントに対処するためです。C#では、ほとんどの言語と同様に、定数はコンパイラによって「折り畳まれます」。たとえば、次のプログラムをコンパイルし、ILを調べます。コンピューターのメモリはもちろんのこと、これらすべての数値がILに到達することさえありません。

public class Program
{
    public static int Main()
    {
        const int a = 1000;
        const int b = a + a;
        const int c = b + 42;
        const int d = 7928345;
        return (a + b + c + d) / (-a - b - c - d);
    }
}

結果のIL:

.method public hidebysig static 
    int32 Main () cil managed 
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.m1  // the constant value -1 to be returned.
    IL_0002: stloc.0
    IL_0003: br.s IL_0005

    IL_0005: ldloc.0
    IL_0006: ret
}

したがって、定数を使用するか、リテラルを使用するか、定数演算を使用するコードのキロバイトを使用するかに関係なく、値はILでそのまま処理されます。

関連するポイント:定数の折りたたみは文字列リテラルに適用されます。多くの人は、このような呼び出しが不必要で非効率的な文字列連結を引き起こしていると信じています:

public class Program
{
    public static int Main()
    {
        const string a = "a";
        const string b = a + a;
        const string c = "C";
        const string d = "Dee";
        return (a + b + c + d).Length;
    }
}

ただし、ILを確認してください。

IL_0000: nop
IL_0001: ldstr "aaaCDee"
IL_0006: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_000b: stloc.0
IL_000c: br.s IL_000e
IL_000e: ldloc.0
IL_000f: ret

結論:定数式の演算子は定数式になり、コンパイラーはすべての計算を行います。実行時のパフォーマンスには影響しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.