什么是神奇数字?

为什么要避免呢?

有没有合适的情况?


当前回答

值得注意的是,有时您确实希望在代码中使用不可配置的“硬编码”数字。有许多著名的,包括0x5F3759DF,它用于优化的平方根反算法。

在极少数情况下,我发现需要使用这种神奇的数字,我将它们设置为我的代码中的const,并记录为什么使用它们,它们是如何工作的,以及它们来自哪里。

其他回答

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 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();
         }
    }
}

魔术数字也可以是具有特殊的硬编码语义的数字。例如,我曾经看到一个系统,其中记录id > 0被正常对待,0本身是“新记录”,-1是“这是根”,-99是“这是在根中创建的”。0和-99将导致WebService提供一个新的ID。

这样做的不好之处在于,您重用了用于特殊功能的空间(记录id的有符号整数的空间)。也许您永远不希望创建ID为0或ID为负的记录,但即使不是这样,每个查看代码或数据库的人都可能在一开始无意中发现这一点并感到困惑。不用说,这些特殊的值并没有得到很好的记录。

可以说,22、7、-12和620也可以算作神奇数字。: -)

@eed3si9n:我甚至认为“1”是一个神奇的数字。: -)

与神奇数字相关的一个原则是,代码处理的每个事实都应该声明一次。如果您在代码中使用神奇的数字(例如@marcio给出的密码长度示例),那么您很容易复制该事实,当您对事实的理解发生变化时,您就会遇到维护问题。

幻数与符号常数:何时替换?

魔法:语义未知

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?

检查代码文本中独立的清单常量基本值。慢慢地、仔细地问每一个问题,每一个例子都有这样的价值。考虑一下你的回答的说服力。很多时候,答案不是非黑即白的,而是有误解的意义和目的、学习速度和理解速度的阴影。还需要了解它是如何与周围的软件机器连接的。

最后,替换的答案是回答(在你的脑海中)读者的优点或缺点来建立联系(例如。“得到它”)。他们越快理解意义和目的,你的“魔力”就越小。

结论:只有当魔法大到难以检测由混淆引起的错误时,才能用符号常数替换基本值。