使用常数是否正确?


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:您确定在这里没有下意识的反应吗?的确,诸如“ 包含”中的-1in 是完全合理的。但是在这里,0的含义既不是显而易见的(与创建新供应商的关系是什么?)也不是真正的常数(如果创建新供应商的方式发生了变化,该怎么办?)。str.indexOf(substr) != -1strsubstr

62
您必须先学会规则,然后才能打破规则
Ryathal 2011年

12
我使这种方法意外失败了。该列表按字母顺序排序。我使用了--创建新--破折号将首先排序,并将硬编码索引0排序到CreateNew方法。然后有人添加了一个以单引号“ My Item”开头的项目,项目在破折号前排序。我的硬编码使程序在下次加载列表时崩溃。我必须手动修改列表的数据文件以恢复客户的数据。
Hand-E-Food

14
您可以改用int.Zero让他高兴的方式:)
Paul Stovell 2011年

Answers:


90

在C#中执行此操作的实际正确方法是完全不依赖ComboItems的顺序

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 2011年

4
@marcof:好吧,如果常量是公共接口的一部分,那么他应该遵循MS命名准则。如果没有,那么他至少应该早日学习最佳实践。
Groky 2011年

2
这仍然是不正确的。它将问题从具有整数文字(零)变为字符串文字(创建新供应商)。充其量,现在的问题是“如果标签改变了怎么办”而不是“如果索引改变了怎么办”。
Freiheit,

7
@Freiheit:不正确。这里的字符串常量仅用于显示;它不会影响程序的逻辑。与使用魔术值/字符串存储程序状态不同,更改此字符串(或从列表中添加/删除内容)永远不会破坏任何内容。
BlueRaja-Danny Pflughoeft

83

组合框中的顺序可能会更改。如果在“创建新供应商...”之前添加其他选项,例如“创建特殊供应商...”,该怎么办?

使用常量的优点是,如果有很多方法取决于组合框的顺序,则只需更改常量即可,而不必更改所有方法。

使用常量也比文字更具可读性。

if (comboVendor.SelectedIndex == NewVendorIndex)

大多数编译语言会在编译时替换常量,因此不会影响性能。


5
我将在value属性中使用描述性代码,而不是依赖可以更改的索引(将create选项移到列表的底部,将完全破坏常量方法)。然后只需查找该代码。
CaffGeek

1
@Chad我同意通过gui中的命令识别控件非常脆弱。如果语言支持将值添加到可以查询的gui元素,则可以使用该值。
Michael Krussel

3
@solution,因为这是约定。
Ikke


1
如果我们谈论的是C#,那么NewVendorIndex是惯例。它与.NET其余样式一致。
2011年

36

您描述的这种情况是一个判断电话,就我个人而言,如果仅使用一次并且已经可读,就不会使用它。

真正的答案是,他选择了这个来教你一堂课。

不要忘记他是一名教授,他的工作是教你编码和最佳实践。

我会说他实际上做得很好。

当然他可能有点绝对,但是我敢肯定,在使用魔术数字之前,您会再考虑一下。

而且,他足够了解您,可以让您加入一个有关程序员的在线社区,以找出在这种情况下什么是最佳实践。

向您的教授致敬。


+1表示在这种情况下,这种手段证明了最终结果的正确性
Oliver-clare

@ Thanos-“ 别忘了他是一名教授,他的工作是教你编码和最佳实践。 ”我的教授从来没有这样。我想说的是,一旦课程创建,他的工作就是教你部门认为什么很重要。
Ramhound 2011年

13

[...]我顶部的选项(索引0,永远不会改变)打开“创建新的供应商”对话”。

您必须解释的事实证明了为什么应该使用常量。如果引入了常量like NEW_VENDOR_DIALOG,则代码将更加不言自明。此外,编译器会优化常量,因此性能不会发生变化。

为程序员而不是编译器编写程序。除非您专门尝试进行微优化,否则这似乎并非您所想。


2
-1甚至表示性能。即使没有优化,一台计算机也可以在用户注意到之前进行数十亿次这样的操作。
鲍里斯·扬科夫

3
@Boris OP似乎担心资源的浪费,这就是我提到它的原因。因此,我看不出我的答案会不太正确。
kba 2011年

12

他声称0应该是一个常数,因此实际上使我停靠了标记。

我同意 在这里使用零是“魔术”。想象一下,您是第一次阅读此代码。您不知道为什么零是特殊的,而字面值却告诉您零为什么是特殊的。如果您不是这么说的if(comboVendor.SelectedIndex == CreateNewVendorIndex)话,那么对于初次阅读者来说,代码的含义就变得非常清楚。

他声称我根本不应该在代码中使用文字。

这是一个极端的立场;现实的情况是,文字的使用是一个危险信号,它表示代码可能不那么清楚。有时这是适当的。

问题是,我不明白为什么要在那种情况下使该代码保持不变。该索引永远不会改变

它永远不会改变是使其保持不变绝佳理由。这就是为什么常量称为常量的原因。因为它们永远不会改变。

也不是您需要调整的东西。

真?您看不到有人想要在组合框中更改事物顺序的任何情况吗?

你可以看到一个原因,在未来,这可能会改变是一个很好的理由,事实上让它恒定。相反,它应该是一个非常量的只读静态整数字段。常量应该是保证保持不变量所有的时间。Pi和金的原子序数是好的常数。版本号不是;他们会更改每个版本。黄金价格显然是一个可怕的常数。它每秒变化一次。只做永远不变的事情。

将一个单独的0保留在用于特定情况且永不更改的内存中似乎浪费内存。

现在我们来解决问题的关键。

这可能是您问题中最重要的一行,因为它表明您对(1)内存和(2)优化有深刻的理解。您正在学校学习,现在将是一个正确了解基本知识的好时机。您能否详细解释为什么您相信“在内存中保留一个零是浪费内存”?首先,为什么您认为在具有至少20亿字节用户可寻址存储的进程中优化四个字节内存的使用意义重大? 第二,您到底想在这里消耗什么资源?您消耗“内存”是什么意思?

我对这些问题的答案很感兴趣,首先是因为它们有机会让您学习对优化和内存管理的理解是不正确的,其次是因为我一直想知道为什么初学者相信奇怪的事情,以便我可以设计更好的工具,使他们有正确的信念。


6

他是对的。你是对的。你错了。

从概念上讲,他是对的,应该避免使用幻数。通过在数字的含义上添加上下文,常数使代码更易读。将来,当有人读取您的代码时,他们会知道为什么要使用特定的号码。而且,如果您需要在某个位置更改某个值,那么最好将其更改为一个位置,而不是尝试在使用特定数字的任何地方进行查找。

话虽这么说,你是对的。在这种情况下,我真的不认为需要常量。您正在寻找列表中的第一项,始终为零。永远不会是23。您正在专门寻找零。我真的不认为您需要通过使其恒定来使代码混乱。

但是,假设常量被当作变量使用,则“使用内存”是错误的。人和编译器都有一个常量。它告诉编译器在编译期间将该值放在该位置,否则您将在原处放置一个文字数字。即使它确实在内存中保持不变,但对于最苛刻的应用程序而言,效率损失甚至是无法衡量的。担心单个整数的内存使用肯定属于“过早优化”。


1
如果不是第三段,我几乎将这个答案投了赞成票,声称使用立即数0足够清楚,并且不需要常量。更好的解决方案将是根本不依赖组合框项目的索引,而是依赖所选项目的。魔术常数是魔术常数,即使它们是0或3.14或诸如此类-请适当命名它们,因为这会使代码更易读。
罗兰·德普

使用列表中的值可能会更好,但这并不是他的问题所在。他的问题是,这是否适合作为常数。而且,如果在与GUI交互的上下文中将0与0进行比较(无论出于何种原因-也许有人在寻找第一项而不管其值如何),我认为就不必在该位置使用常量。
GrandmasterB

我不会同意...从仅看代码来看,永远不会立即知道0的含义是什么-可能确实是他一直在寻找列表中的第一项,就是这样,但是随后如果比较的结果是'comboVendor.SelectedIndex == FirstIndex',它将更清晰些?
罗兰·特普

2

我会0用常量代替,以使含义更清楚,例如NewVendorIndex。您永远不知道您的订单是否会改变。


1

那是你教授的全部偏好。通常,仅在常量要多次使用的情况下才使用常量,您想使读者知道行的用途,否则将来可能会更改常量,而您只想更改它放在一个地方。但是,在本学期,教授是老板,所以从现在开始我要在那堂课上做。

为企业界提供良好的培训?很有可能。


2
我不同意“如果文字会被多次使用,则仅使用常量”部分。使用0(也许是-1、1)通常是很清楚的,在大多数情况下,给事物起个名字是一件好事,因为在阅读代码时会更清楚。
johannes

1

老实说,虽然我认为您的代码不是最佳实践,但坦率地说,他的建议有点怪诞。

.NET组合框的更常见做法是为“ Select ..”项提供一个空值,而实际项具有有意义的值,然后执行以下操作:

if (string.IsNullOrEmpty(comboVendor.SelectedValue))

而不是

if (comboVendor.SelectedIndex == 0)

3
在您的示例中,null只是另一个文字。本课的核心是应避免使用文字。
2011年

@overslacked-在我的示例中没有空文字。
Carson63000

即使存在null文字,null也是可以使用的可接受文字,我什至不相信有一种方法可以检查对一个对象的引用是否为null而不使用检查其是否等于null。
Ramhound,2011年

Carson63000-我指的是您对IsNullOrEmpty的使用。您正在将一个“魔术值”换成另一个。@Ramhound-在某些情况下,Null是可以接受的文字,毫无疑问,但是我不认为这是一个很好的例子(使用null或空白作为魔术值)。
2011年

-1为“有点怪诞”。虽然你说得对,使用值会比较常规的,更容易理解,如果使用的selectedIndex,使用命名常量肯定比0刚刚是
jmoreno

1

他强调使用常量的值并没有错,您使用文字也没有错。除非他强调这是预期的编码风格,否则不要因为使用文字而对它们造成伤害,因为它们不会有害。我已经看到文字在商业代码中到处使用了很多次。

他的观点虽然很好。这可能是使您意识到常量的好处的方式:

1-他们在某种程度上保护您的代码免遭意外篡改

2-就像@DeadMG在他的答案中说的那样,如果在许多地方使用相同的文字值,它可能会错误地显示为不同的值-因此常量可以保持一致性。

3-常量保留类型,因此您不必使用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天,而不是7.4287143
Winston Ewert

@WinstonEwert,感谢您的评论。365/52 = 7.01923076923077 = 7 +(1/52)。现在,如果删除小数部分并计算出7 * 52,您将获得364天,这不是一年中的正确天数。分数小数要比丢失分数小得多(因为如果只想显示数字,则可以将结果格式化为显示7)。无论如何,这只是MS中有关常量的一个示例,但是您的观点很有趣。
NoChance 2011年

1
当然,这只是一个例子,但我反对您的陈述,将分数包含在内更为准确。一周定义为7天。根据您的定义,26周为182.5天,这根本不准确。确实,问题出在您int weeks = 52,每年没有52周。一年中有52.142857142857146周,那就是您应该保留分数的数字。当然,在整个常数集中,唯一不变的是月数。
温斯顿·埃韦特

0

仅当它具有复杂的导数或经常重复时,才需要将其置于常量中。否则,文字就可以了。将所有内容保持不变是完全过分的。


只有当您三思而后行,并且您永远不需要修改代码。通过更改两种情况之一来创建错误确实很容易。
BillThor 2011年

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
可能是个好方法,但是看到html并不是他最有希望的示例代码,因为他正在使用C#。
温斯顿·埃韦特

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

底线:常量表达式的运算符产生常量表达式,并且编译器执行所有计算;它不会影响运行时性能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.