什么是神奇数字?

为什么要避免呢?

有没有合适的情况?


当前回答

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

魔法:语义未知

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?

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

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

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

其他回答

我总是以不同的方式使用术语“魔数”,作为存储在数据结构中的模糊值,可以作为快速有效性检查进行验证。例如,gzip文件的前三个字节包含0x1f8b08, Java类文件以0xcafebabe开头,等等。

您经常看到在文件格式中嵌入了神奇的数字,因为文件可以相当混乱地四处发送,并丢失关于它们如何创建的任何元数据。然而,魔法数字有时也用于内存中的数据结构,如ioctl()调用。

在处理文件或数据结构之前快速检查这个神奇的数字,可以尽早发现错误,而不是为了宣布输入完全是胡编乱作而进行可能很长的处理。

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

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

魔术数字是在代码中直接使用数字。

例如,如果你有(在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这样的静态分析工具可以检测到代码中使用的神奇数字,并建议进行重构。

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

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

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

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

魔法:语义未知

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?

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

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

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