什么是神奇数字?
为什么要避免呢?
有没有合适的情况?
什么是神奇数字?
为什么要避免呢?
有没有合适的情况?
当前回答
Magic Number是一个硬编码的值,它可能在以后的阶段更改,但因此很难更新。
例如,假设您有一个页面,在“您的订单”概览页面中显示最近50个订单。50在这里是一个神奇的数字,因为它不是通过标准或惯例设置的,它是您根据规范中概述的原因虚构的数字。
现在,你要做的是你在不同的地方有50个-你的SQL脚本(SELECT TOP 50 * FROM orders),你的网站(你的最后50个订单),你的订单登录(for (i = 0;I < 50;i++))和可能的许多其他地方。
那么,如果有人决定把50岁改成25岁,会发生什么呢?还是75年?还是153年?现在你必须在所有的地方替换50,你很可能会错过它。Find/Replace可能不起作用,因为50可能用于其他事情,盲目地将50替换为25可能会产生一些其他不良副作用(即你的Session)。Timeout = 50呼叫,也设置为25,用户开始报告太频繁的超时)。
此外,代码可能很难理解,即。"if a < 50那么bla"——如果你在一个复杂的函数中遇到这种情况,其他不熟悉代码的开发人员可能会问自己"WTF是50?? "
这就是为什么最好在1个地方有这样模糊和任意的数字-“const int NumOrdersToDisplay = 50”,因为这使代码更具可读性(“如果< NumOrdersToDisplay”,这也意味着你只需要在1个定义良好的地方更改它。
适用Magic Numbers的地方是通过标准定义的所有内容,即SmtpClient。DefaultPort = 25或TCPPacketSize =任何(不确定是否标准化)。此外,只在一个函数中定义的所有内容都可能是可接受的,但这取决于上下文。
其他回答
Magic Number是一个硬编码的值,它可能在以后的阶段更改,但因此很难更新。
例如,假设您有一个页面,在“您的订单”概览页面中显示最近50个订单。50在这里是一个神奇的数字,因为它不是通过标准或惯例设置的,它是您根据规范中概述的原因虚构的数字。
现在,你要做的是你在不同的地方有50个-你的SQL脚本(SELECT TOP 50 * FROM orders),你的网站(你的最后50个订单),你的订单登录(for (i = 0;I < 50;i++))和可能的许多其他地方。
那么,如果有人决定把50岁改成25岁,会发生什么呢?还是75年?还是153年?现在你必须在所有的地方替换50,你很可能会错过它。Find/Replace可能不起作用,因为50可能用于其他事情,盲目地将50替换为25可能会产生一些其他不良副作用(即你的Session)。Timeout = 50呼叫,也设置为25,用户开始报告太频繁的超时)。
此外,代码可能很难理解,即。"if a < 50那么bla"——如果你在一个复杂的函数中遇到这种情况,其他不熟悉代码的开发人员可能会问自己"WTF是50?? "
这就是为什么最好在1个地方有这样模糊和任意的数字-“const int NumOrdersToDisplay = 50”,因为这使代码更具可读性(“如果< NumOrdersToDisplay”,这也意味着你只需要在1个定义良好的地方更改它。
适用Magic Numbers的地方是通过标准定义的所有内容,即SmtpClient。DefaultPort = 25或TCPPacketSize =任何(不确定是否标准化)。此外,只在一个函数中定义的所有内容都可能是可接受的,但这取决于上下文。
在类的顶部用默认值初始化一个变量怎么样?例如:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
在这种情况下,15000是一个神奇的数字(根据CheckStyles)。对我来说,设置一个默认值是可以的。我不想做的事情是:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
这会让它更难读吗?在安装CheckStyles之前,我从未考虑过这一点。
魔术数字是在代码中直接使用数字。
例如,如果你有(在Java中):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
这应该被重构为:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
它提高了代码的可读性,也更容易维护。想象一下我在GUI中设置密码字段大小的情况。如果我使用一个神奇的数字,每当最大大小发生变化时,我必须在两个代码位置进行更改。如果我忘记了一个,就会导致不一致。
JDK中有很多例子,比如Integer, Character和Math类。
PS:像FindBugs和PMD这样的静态分析工具可以检测到代码中使用的神奇数字,并建议进行重构。
幻数与符号常数:何时替换?
魔法:语义未知
Symbolic Constant ->为使用提供正确的语义和上下文
语义:事物的意义或目的。
“创建一个常数,用它的含义命名它,并用它替换数字。”——马丁·福勒
首先,神奇数字不仅仅是数字。任何基本的价值都可以是“魔法”。基本值是显式实体,如整数、实数、双精度数、浮点数、日期、字符串、布尔值、字符等。问题不是数据类型,而是在代码文本中出现的值的“魔力”方面。
我们所说的“魔法”是什么意思?准确地说:通过“魔术”,我们打算在代码上下文中指出值的语义(含义或目的);它是未知的,不可知的,不清楚的,或令人困惑的。这就是“魔法”的概念。当一个基本值的语义或存在目的——在没有特殊辅助词(例如符号常量)的情况下,从周围的上下文中就可以快速、容易地知道、清楚和理解(而不是混淆)时,它就不是神奇的。
因此,我们通过测量代码读者从周围上下文了解、清晰和理解基本值的意义和目的的能力来识别神奇数字。读者越不了解、越不清楚、越困惑,基本价值就越“神奇”。
基础知识
我们有两种神奇的基本价值观。对于程序员和代码来说,只有第二个才是最重要的:
一个单独的基本值(如数字),其含义是未知的、不可知的、不清楚的或令人困惑的。 上下文中的基本值(例如数字),但其含义仍然未知、不可知、不清楚或令人困惑。
“magic”的主要依赖关系是,一个基本值(例如number)没有常见的语义(如Pi),但有一个局部已知的语义(例如您的程序),从上下文来看不完全清楚,或者在好或坏的上下文中可能被滥用。
大多数编程语言的语义不允许我们单独使用基本值,除非(可能)作为数据(即数据表)。当我们遇到“神奇数字”时,我们通常会在某个上下文中这样做。因此,答案是
"我要用符号常数替换这个神奇的数字吗"
is:
“你能多快地评估和理解的语义 它存在的目的是什么?”
有点魔力,但不完全是
有了这个想法,我们可以很快看到像圆周率(3.14159)这样的数字在适当的环境下(例如2 x 3.14159 x半径或2 pir)不是一个“神奇的数字”。在这里,数字3.14159是没有符号常量标识符的圆周率。
不过,由于数字的长度和复杂性,我们通常将3.14159替换为像Pi这样的符号常量标识符。圆周率的长度和复杂性(加上对精度的需求)通常意味着符号标识符或常数不太容易出错。“π”作为一个名字只是一个方便的奖励,但不是有这个常数的主要原因。
同时:回到牧场
撇开像π这样的常见常数不谈,让我们主要关注具有特殊含义的数字,但这些含义受限于我们的软件系统。这样的数字可能是“2”(作为一个基本整数值)。
如果我单独使用数字2,我的第一个问题可能是:“2”是什么意思?“2”本身的意义在没有上下文的情况下是未知和不可知的,这使得它的使用不清楚和令人困惑。尽管由于语言语义的原因,在我们的软件中不会出现“2”,但我们确实希望看到“2”本身没有特殊的语义或明显的目的。
让我们把唯一的“2”放在:padding:= 2的上下文中,其中上下文是“GUI Container”。在这种情况下,2的含义(作为像素或其他图形单位)为我们提供了对其语义(含义和目的)的快速猜测。我们可以停在这里,说2在这种情况下是可以的,没有其他需要知道的了。然而,在我们的软件领域,这可能不是全部。还有更多,但“padding = 2”作为上下文不能揭示它。
让我们进一步假设在程序中2作为像素填充在整个系统中是“default_padding”类型。因此,写入指令padding = 2是不够的。“违约”的概念并没有被揭示。只有当我写:padding = default_padding作为上下文,然后在其他地方写:default_padding = 2时,我才完全意识到2在我们的系统中更好和更充分的意义(语义和目的)。
The example above is pretty good because "2" by itself could be anything. Only when we limit the range and domain of understanding to "my program" where 2 is the default_padding in the GUI UX parts of "my program", do we finally make sense of "2" in its proper context. Here "2" is a "magic" number, which is factored out to a symbolic constant default_padding within the context of the GUI UX of "my program" in order to make it use as default_padding quickly understood in the greater context of the enclosing code.
因此,任何基本值,其含义(语义和目的)不能被充分和快速地理解,都可以用符号常数来代替基本值(例如幻数)。
要进一步
刻度上的数字也可能有语义。例如,假设我们正在制作一款《龙与地下城》游戏,其中有一个怪物的概念。我们的怪物对象有一个叫做life_force的特性,它是一个整数。这些数字的含义如果没有文字来解释,是无法理解或清楚的。因此,我们一开始就武断地说:
full_life_force: INTEGER = 10—非常活跃(和未受伤) minimum_life_force: INTEGER = 1—勉强活着(非常受伤) 死亡:INTEGER = 0 -死亡 undead: INTEGER = -1—最小undead(几乎死亡) 僵尸:INTEGER = -10—最大不死(非常不死)
From the symbolic constants above, we start to get a mental picture of the aliveness, deadness, and "undeadness" (and possible ramifications or consequences) for our monsters in our D&D game. Without these words (symbolic constants), we are left with just the numbers ranging from -10 .. 10. Just the range without the words leaves us in a place of possibly great confusion and potentially with errors in our game if different parts of the game have dependencies on what that range of numbers means to various operations like attack_elves or seek_magic_healing_potion.
因此,当搜索和考虑替换“魔术数字”时,我们希望在我们的软件上下文中询问关于数字的非常有目的性的问题,甚至是数字如何在语义上相互作用。
结论
让我们回顾一下我们应该问什么问题:
你可能有一个神奇的数字,如果……
Can the basic value have a special meaning or purpose in your softwares universe? Can the special meaning or purpose likely be unknown, unknowable, unclear, or confusing, even in its proper context? Can a proper basic value be improperly used with bad consequences in the wrong context? Can an improper basic value be properly used with bad consequences in the right context? Does the basic value have a semantic or purpose relationships with other basic values in specific contexts? Can a basic value exist in more than one place in our code with different semantics in each, thereby causing our reader a confusion?
检查代码文本中独立的清单常量基本值。慢慢地、仔细地问每一个问题,每一个例子都有这样的价值。考虑一下你的回答的说服力。很多时候,答案不是非黑即白的,而是有误解的意义和目的、学习速度和理解速度的阴影。还需要了解它是如何与周围的软件机器连接的。
最后,替换的答案是回答(在你的脑海中)读者的优点或缺点来建立联系(例如。“得到它”)。他们越快理解意义和目的,你的“魔力”就越小。
结论:只有当魔法大到难以检测由混淆引起的错误时,才能用符号常数替换基本值。
提取一个神奇数字作为常数的另一个优点是可以清楚地记录业务信息。
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}