作为经验丰富的软件开发人员,我学会了避免使用魔术字符串。
我的问题是,自从我使用它们已经有这么长时间了,我已经忘记了大多数原因。结果,我无法向经验不足的同事们解释为什么它们是一个问题。
有什么客观原因可以避免它们?它们会导致什么问题?
作为经验丰富的软件开发人员,我学会了避免使用魔术字符串。
我的问题是,自从我使用它们已经有这么长时间了,我已经忘记了大多数原因。结果,我无法向经验不足的同事们解释为什么它们是一个问题。
有什么客观原因可以避免它们?它们会导致什么问题?
Answers:
在可编译的语言中,不可在编译时检查魔术字符串的值。如果字符串必须匹配特定的模式,则必须运行程序以确保它适合该模式。如果使用了枚举之类的值,则该值至少在编译时有效,即使它可能是错误的值也是如此。
如果在多个位置编写了魔术字符串,则必须在没有任何安全性(例如编译时错误)的情况下更改所有字符串。可以通过仅在一个地方声明它并重新使用该变量来解决这个问题。
错别字可能会成为严重的错误。如果具有功能:
func(string foo) {
if (foo == "bar") {
// do something
}
}
有人不小心输入:
func("barr");
字符串越稀有或更复杂,就越糟,尤其是如果您有不熟悉项目本地语言的程序员。
魔术弦很少能自我记录。如果您看到一个字符串,那么该字符串将不会告诉您/应该是什么。您可能必须研究实现以确保您选择了正确的字符串。
这种实现是泄漏性的,需要外部文档或访问代码来理解应写的内容,特别是因为它必须是完美的字符(如第3点所示)。
IDE中缺少“查找字符串”功能,只有少数工具支持该模式。
您可能会偶然在两个地方使用相同的魔术弦,而实际上它们是不同的东西,因此,如果您执行“查找并替换”并更改了两者,则其中一个可能会损坏,而另一个则会工作。
其他答案已经抓住的最高点不是“魔术值”是坏的,而是它们应该是:
通常将可接受的“常量”与“魔术值”区分开的是对这些规则中的一个或多个规则的某种违反。
很好地使用常量可以使我们表达代码的某些公理。
这使我得出最后一个结论,即过度使用常量(因此,过多地使用值表示的假设或约束),即使它符合上述标准(尤其是偏离标准的情况),可能意味着正在设计的解决方案不够通用或结构良好(因此,我们不再真正在谈论常量的利弊,而只是在讨论结构良好的代码的利弊)。
高级语言具有使用较低级语言的模式的构造,这些构造必须使用常量。相同的模式也可以在高级语言中使用,但不应使用。
但这可能是基于对所有情况的印象以及解决方案的模样的专家判断,而该判断的合理依据将在很大程度上取决于上下文。的确,就任何一般原则而言,这也许是没有道理的,只是断言“我已经年纪大了,已经看过这种工作,我熟悉,做得更好”!
编辑:接受了一项编辑,拒绝了另一项编辑,并且现在执行了我自己的编辑,现在我可以考虑一劳永逸地解决我的规则列表的格式和标点样式!
value / 2
,而不是value / VALUE_DIVISOR
在2
其他地方定义后者。如果打算泛化处理CSV的方法,则可能希望将分隔符作为参数传递,而根本不定义为常量。但这只是上下文中的判断问题- SPEED_OF_LIGHT
您想要明确命名@WGroleau的示例,但并非每个文字都需要此名称。
实际示例:我正在使用第三方系统,其中“实体”与“字段”一起存储。基本上是EAV系统。由于添加另一个字段非常容易,因此您可以使用该字段的名称作为字符串来访问该字段:
Field nameField = myEntity.GetField("ProductName");
(注意魔术字符串“ ProductName”)
这可能会导致几个问题:
因此,我的解决方案是为这些名称生成按实体类型组织的常量。所以现在我可以使用:
Field nameField = myEntity.GetField(Model.Product.ProductName);
它仍然是一个字符串常量,可以编译为完全相同的二进制文件,但是具有几个优点:
在我的列表中的下一个:将这些常量隐藏在生成的强类型类后面-然后也保护数据类型。
nameField = myEntity.ProductName;
。
魔术弦并不总是不好的,因此这可能是您无法提出避免它们的全面原因的原因。(通过“魔术字符串”,我假设您是将字符串文字表示为表达式的一部分,而不是定义为常量。)
在某些特定情况下,应避免使用魔术弦:
但是在某些情况下,“魔术弦”是可以的。假设您有一个简单的解析器:
switch (token.Text) {
case "+":
return a + b;
case "-":
return a - b;
//etc.
}
这里确实没有魔术,并且上述问题均不适用。恕我直言,定义string Plus="+"
等不会有任何好处。请保持简单。
if (dx != 0) { grad = dy/dx; }
。
"+"
,并"-"
与TOKEN_PLUS
和TOKEN_MINUS
。每次阅读它,我都会因此而感到难以阅读和调试!绝对是我同意使用简单字符串更好的地方。
要添加到现有答案中:
如果要在屏幕上显示的文本是经过硬编码的并且埋藏在功能层中,那么将文本翻译成其他语言的时间将非常困难。
一些开发环境(例如Qt)通过从基本语言文本字符串到已翻译语言的查找来处理翻译。魔术字符串通常可以幸免于此-直到您决定要在其他地方使用相同的文本并输入错误为止。即使这样,当您要添加对另一种语言的支持时,也很难找到需要翻译的魔术字符串。
某些开发环境(例如MS Visual Studio)采用另一种方法,要求所有转换后的字符串都保存在资源数据库中,并通过该字符串的唯一ID读取当前语言环境。在这种情况下,带有魔术字符串的应用程序无法简单地将其翻译成另一种语言。高效的开发要求所有文本字符串都必须输入到资源数据库中,并在首次编写代码时具有唯一的ID,此后相对容易。事实发生后尝试回填通常需要付出很大的努力(是的,我去过那里!),因此首先做好事情要好得多。