魔术字符串/数字的用法


31

这是一个颇具争议的话题,我想与程序员一样多。但是,为此,我想知道业务(或您的工作场所)中的常见做法。

在我的工作场所,我们有严格的编码准则。其中一部分专门用于魔术字符串/数字。它指出(对于C#):

除了定义符号常量外,请勿在代码中使用数字或字符串的文字值。使用以下模式定义常量:

public class Whatever  
{  
   public static readonly Color PapayaWhip = new Color(0xFFEFD5);  
   public const int MaxNumberOfWheels = 18;  
}

有例外:几乎可以始终安全地使用值0、1和null。通常,值2和-1也可以。用于记录或跟踪的字符串不受此规则的限制。当文字的含义在上下文中很清楚且不受将来的更改时,允许使用文字。

mean = (a + b) / 2; // okay  
WaitMilliseconds(waitTimeInSeconds * 1000); // clear enough

理想的情况是在以下情况下一些官方研究论文对代码的可读性/可维护性产生影响:

  • 魔术数字/字符串无处不在
  • 魔术字符串/数字会被常量声明合理地替换(或在不同程度的覆盖范围内)-请不要因为使用“合理地”而对我大喊大叫,我知道每个人都有不同的想法,“合理地”是什么
  • 魔术字符串/数字被多余地替换在不需要的地方(请参见下面的示例)

在与一位同事争论时,我希望这样做具有一些基于科学的论据,而后者将要声明常量,例如:

private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

另一个例子是(这是在JavaScript中):

var someNumericDisplay = new NumericDisplay("#Div_ID_Here");

如果仅在1个地方使用DOM ID,您是否将DOM ID粘贴在JavaScript文件的顶部?

我阅读了以下主题:
StackExchange
StackOverflow
字节数IT社区
还有很多文章,阅读这些文章后,就会出现一些模式。

所以我的问题是在代码中使用魔术字符串和数字吗?我特意在寻找可能的参考文献支持的专家解答。


2
魔术变量是一种变量,其含义未由其内容反映。整数值“ 10”反映了数字10的含义,因此无需使其恒定。空间和分号也一样。另一方面,如果您有一个值'%% ?? %%'并且这是一些自定义分隔符,则将HAS放置为常量,因为其内容未反映出它是分隔符的事实。
Jeroen Vannevel 2013年

23
NumberTen = 10这毫无意义,因为不会重新定义数字10。MaxRetryCount = 10有一点我们可能要更改最大重试次数。private const char SemiColon = ';'; 哑。private const char LineTerminator = ';'; 聪明。
麦克,

1
实际问题尚不清楚。
图兰斯·科尔多瓦

Answers:


89

...与我的一位同事吵架时,谁将要声明常量,例如:

private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

您需要与同事讨论的论点不是关于将文字空间命名为,Space而是他为常数选择的名字很差。

假设您的代码的工作是解析记录流,这些记录流包含由分号(a;b;c)分隔并且由空格(a;b;c d;e;f)分隔的字段。如果撰写规范的人从现在起一个月后打电话给您,并说:“我们错了,记录中的字段用竖线符号(a|b|c d|e|f)分隔”,您该怎么办?

在您的同事喜欢的“按价值命名”方案下,您必须更改文字(SemiColon = '|')的值,并使用继续SemiColon用于不再是分号的代码。这将导致代码审查中的负面评论。要消除这种情况,您可以将文字名称更改为,PipeSymbol并通过更改每次出现的方式SemiColon来更改PipeSymbol。以这种速度,您可能还应该首先使用文字分号(';'),因为您必须分别评估它的每次使用,并且要进行相同数量的更改。

为常数标识符需要是描述性的价值是什么,这个值不是,那您的同事做了一个左转进入杂草的。在上述的字段拆分应用程序中,分号的用途是字段分隔符,应相应地命名常量:

private const char FieldSeparator = ';';    // Will become '|' a month from now
private const char RecordSeparator = ' ';
private const int MaxFieldsPerRecord = 10;

这样,当字段分隔符更改时,您只需更改一行代码,即常量的声明。看到更改的人只会看到那一行,并且会立即了解到,字段分隔符已从分号更改为管道符号。其余代码(由于使用了常量而无需进行更改)保持不变,并且读者不必深入研究它就可以做什么。


我完全同意。几十年前,我从事一个项目,该项目按段发送消息,每个消息使用8个通用寄存器。有人声明过 #define one 1 #define two 2 等(或当时选择的语言是UK Post Office Coral)。从高处传来的消息是,将来长度字段将是字节数,而不是段数,因此显然代码已更改为 #define one 8 #define two 16 etc
Mawg

3
至于愚蠢像分号或PipeSymbol名字看起来,使用脚本将不是改变每个受影响的容易得多改变一个到另一个;|
布兰丁

如果在文件中多次使用给定的String文字,但是除了其值之外没有其他意义的情况,该怎么办?例如,如果您要测试可以在20种不同情况下在地图中接收某个键,那么我是否应该像这样定义一个常数?: public static final String MY_KEY_NAME = "MyKeyName"
Jordan McQueen

1
@JordanMcQueen有一种情况需要使用裸字面值,前提是(且仅当)每个仅被使用一次且在其他任何地方都不需要时。如果这件事情就像每个场景之中的代码,处理不同的文件格式,每种格式应该定义自己的常数(如CSV_RECORD_SEPARATORTSV_RECORD_SEPARATOR等)。
Blrfl

8

将分号定义为常数是多余的,因为分号本身已经是常数。它永远不会改变。

好像没有一天有人会宣布“术语更改,+现在是新的分号”,而您的同事会高兴地急于更新常数(他们嘲笑我-现在看看它们)。

还有一个一致性问题。我保证NumberTen不会为每个人使用他的常量(大多数编码人员都不在意),因此无论如何它都不会达到预期的目的。当末日降临并且“十”被全局重新缩放为9时,更新常量将无法解决问题,因为它仍然会10在代码中留下大量的文字s,因此即使在范围内,系统现在也变得完全不可预测一个革命性的假设,即“十”表示“ 9”。

将所有设置存储为const是我也要考虑的另一件事。一个人不应轻易做到这一点。

到目前为止,我们收集了哪些此类使用示例?线路终止器...最大重试次数...最大轮数...我们确定这些不会改变吗?

代价是更改默认设置需要重新编译应用程序,在某些情况下甚至需要重新编译其依赖项(因为数字const值可能在编译过程中被硬编码)。

还有测试和模拟方面。您已将连接字符串定义为const,但现在哎呀,您不能在单元测试中模拟数据库访问(建立伪连接)。


4
“它永远不会改变。” 我曾经认为单引号(永远与ASCII值39挂钩)。一些旧的应用程序曾经使撇号变小。但是现在,现代应用程序将ASCII值视为与旧应用程序兼容的单引号,而人们经常将兼容的应用程序(左单引号,Unicode 8217)用于显示卷曲标记的不同字形的应用程序。由于欧洲使用逗号作为美国人使用句点作为小数点的方式,所以我发现自己有点犹豫地宣布“从不...从不”。
TOOGAM

@TOOGAM好,有你的例子证明一个DecimalPoint常数-而不是CommaPeriod常量。这是完全不同的:前者表示值的功能,角色或目的。“分号”或“逗号”不属于该类别。
康拉德莫拉斯基

小数点示例就是如此。但是,撇号示例似乎确实与逗号(或分号)相似(或相同)。
TOOGAM

@KonradMorawski分号可用于许多目的,例如拆分字符串或结束行。应该使用它的含义(而不是值)来命名常量。考虑未来的变化,也就是明天,我们允许20个记录的过程,所以命名为康斯坦斯NumberTen是环境背景的,而maxRecord将仍然有效。
MaxZoom

5
private const char SemiColon = ';';
private const char Space = ' ';
private const int NumberTen = 10;

因此,您的同事的目标是每日参加WTF。这些定义是愚蠢的和多余的。但是,正如其他人所指出的那样,以下定义不会是愚蠢的或多余的:

private const char StatementTerminator = ';';
private const char Delimiter = ' ';
private const int  BalanceInquiryCode = 10;

“魔术”数字和字符串是常量,其含义超出其立即的字面值。如果该常量的10含义超出“十件事”(例如,作为特定操作或错误条件的代码),则该常量变为“魔术”,应替换为描述该抽象含义的符号常量。

除了清楚地描述意图,符号常量还为您拼写错误的文字省去了一些麻烦。在一行代码中,从“ CVS”到“ CSV”的简单转换就一直进行单元测试和质量检查,并投入生产,从而导致特定操作失败。是的,很显然,单元测试和QA测试是不完整的,这是它自己的问题,但是使用符号常量可以完全避免这种胃灼热的感觉。


3

对此不应该有任何争议。关键不是要使用魔术数字,还是要具有可读的代码。
考虑之间的区别:if(request.StatusCode == 1)if(request.HasSucceeded)。在这种情况下,我认为后者更具可读性,但这并不意味着您永远都不会拥有像这样的代码int MaxNumberOfWheels = 18

PS:这就是为什么我绝对讨厌编码指南。开发人员应该足够成熟,能够做出这样的判断。他们不应该把它留给上帝知道谁的文本形成。


13
驾驶员应该足够成熟,以便能够判断驾驶的方向;)
Konrad Morawski 2013年

2
甚至在成熟的开发人员之间,判断调用的结果也可能会有所不同,因此,即使是任意编码准则也旨在通过一致性来提高可读性。这与创建常量NumberTen没有意义这一事实无关。
Mike Partridge 2013年

1
我不会坚持要求它们必须是正式的,加盖章等的,它们可以是非正式的,但应该得到他们的同意,而这已经不仅仅是仅仅依靠个人的判断力。但是您现在删除了评论Stefan :)
Konrad Morawski 2013年

1
@StefanBilliet-一点也不。我的观点是,通过一致性可以提高可读性。这里的问题不是编码准则本身,而是通过误解而达到极端的准则。
Mike Partridge 2013年

@MikePartridge也许我应该详细说明;我所看到的编码指南更多地是关于某人认为应该编写软件的通用规则手册的趋势,而不是像您和Konrad这样的协议可能想到的:-)
Stefan Billiet 2013年
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.