What is a magic number, and why is it bad?
什么是幻数?
为什么要避免?
是否有合适的案例?
幻数是代码中数字的直接用法。
例如,如果你有(在爪哇):
1 2 3 4 5 6 7 8 | public class Foo { public void setPassword(String password) { // don't do this if (password.length() > 7) { throw new InvalidArgumentException("password"); } } } |
应将其重构为:
1 2 3 4 5 6 7 8 9 | 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中有很多例子,比如在
PS:静态分析工具(如findbugs和pmd)可以检测代码中使用的幻数,并建议进行重构。
幻数是一个硬编码的值,在以后的阶段可能会改变,但因此很难更新。
例如,假设您有一个页面,在"您的订单"概述页面中显示最后50个订单。50是这里的神奇数字,因为它不是通过标准或约定来设置的,它是您出于规范中概述的原因而编出来的数字。
现在,您要做的是在不同的地方使用这50个脚本—您的SQL脚本(
现在,当某人决定将50改为25时会发生什么?还是75?还是153?现在你必须在所有地方更换50个,你很可能会错过。查找/替换可能不起作用,因为50可能用于其他事情,盲目地将50替换为25可能会产生一些其他不良的副作用(即,您的
此外,代码可能很难理解,即"
这就是为什么最好在一个地方有这样的模糊和任意的数字——"
幻数适用的地方是通过标准定义的所有内容,即
你看过维基百科的魔法数字条目了吗?
它详细介绍了magic数字引用的所有方法。这里有一个关于幻数的引述,它是一种糟糕的编程实践
The term magic number also refers to the bad programming practice of using numbers directly in source code without explanation. In most cases this makes programs harder to read, understand, and maintain. Although most guides make an exception for the numbers zero and one, it is a good idea to define all other numbers in code as named constants.
幻数与符号常量:何时替换?
魔法:未知语义好的。
符号常量->提供正确的语义和正确的上下文以供使用好的。
语义:事物的意义或目的。好的。
"创建一个常量,按其含义命名,并用它替换数字。"——马丁·福勒好的。
首先,神奇的数字不仅仅是数字。任何基本价值都可以是"魔法"。基本值是表示实体,如整数、实数、双精度、浮点数、日期、字符串、布尔值、字符等。问题不是数据类型,而是值在代码文本中出现的"魔力"方面。好的。
"魔法"是什么意思?准确地说:通过"魔法",我们打算指出代码上下文中的值的语义(含义或目的);它是未知的、不可知的、不清楚的或混淆的。这就是"魔法"的概念。当基本值的语义含义或存在的目的在没有特殊帮助词(如符号常量)的情况下从环绕上下文中快速、容易地被了解、清晰和理解(不混淆)时,它就不是魔法。好的。
因此,我们通过测量代码阅读器从周围环境中了解、清楚和理解基本值的含义和目的的能力来识别幻数。读者越不了解、越不清楚、越困惑,其基本价值就越"神奇"。好的。有用的定义
- 使困惑:使(某人)变得困惑或困惑。
- 困惑:使(某人)变得困惑和困惑。
- 困惑:完全困惑;非常困惑。
- 困惑:完全困惑或困惑。
- 困惑:无法理解;困惑。
- 理解:理解(单词、语言或说话者)的意图。
- 意思:单词、文本、概念或动作的意思。
- 意思:打算传达、表明或提及(一个特定的事物或概念);表示。
- 表示:表示。
- 指示:指示某物的标志或信息。
- 表示:指出;显示。
- 符号:一个物体、质量或事件,其存在或发生表示可能存在或发生其他事物。
基础
我们有两个魔术基本价值的场景。只有第二个对程序员和代码最重要:好的。
"magic"的总体依赖性是指单个基本值(例如数字)没有常见的语义(如pi),但有一个本地已知的语义(例如程序),它从上下文中不完全清楚,或者可能在好或坏的上下文中被滥用。好的。
大多数编程语言的语义将不允许我们使用单独的基本值,除了(可能)作为数据(即数据表)。当我们遇到"神奇数字"时,我们通常在一个上下文中这样做。因此,答案是好的。
"Do I replace this magic number with a symbolic constant?"
Ok.
是:好的。
"How quickly can you assess and understand the semantic meaning of the
number (its purpose for being there) in its context?"Ok.
有点神奇,但不完全是
考虑到这一点,我们可以很快看到像pi(3.14159)这样的数字在适当的上下文中(例如2 x 3.14159 x radius或2*pi*r)不是"幻数"。在这里,数字3.14159是不带符号常量标识符的思想上可识别的π。好的。
不过,由于数字的长度和复杂性,我们通常用符号常量标识符(如pi)替换3.14159。pi的长度和复杂性方面(加上对准确性的需求)通常意味着符号标识符或常量不太容易出错。将"pi"识别为一个名称只是一个方便的奖励,但并不是拥有常量的主要原因。好的。同时:回到牧场
抛开像pi这样的常见常量,让我们主要关注具有特殊含义的数字,但这些含义仅限于我们软件系统的宇宙。这样的数字可能是"2"(作为基本整数值)。好的。
如果我单独使用数字2,我的第一个问题可能是:"2"是什么意思?"2"本身的含义是未知的,没有上下文是不可知的,它的使用不清楚和混乱。尽管在我们的软件中只有"2"不会因为语言语义而发生,但我们确实希望看到"2"本身没有特殊的语义或明显的目的。好的。
让我们把我们唯一的"2"放在:
让我们进一步假设在我们的程序中,2作为像素填充在整个系统中是"默认填充"类型。因此,编写
上面的例子很好,因为"2"本身可以是任何东西。只有当我们把理解的范围和领域限制在"我的程序"中,其中2是"我的程序"的GUI UX部分中的
因此,任何基本值,其意义(语义和目的)不能充分和迅速地理解,是一个很好的候选者,一个符号常数在基本值(如幻数)的地方。好的。走得更远
规模上的数字也可能有语义。例如,假设我们正在制作一个D&D游戏,在这个游戏中我们有一个怪物的概念。我们的怪物有一个叫做
- 全寿命力:整数=10——非常有活力(并且没有受伤)
- 最小生存力:整数=1——勉强活着(非常受伤)
- 死:整数=0--死
- undead:integer=-1--最小undead(几乎死了)
- 僵尸:整数=-10--最大不死(非常不死)
从上面的符号常量中,我们开始从精神上了解D&D游戏中怪物的活力、死亡和"不适应"(以及可能的后果)。如果没有这些词(符号常量),我们只剩下
因此,在搜索和考虑替换"幻数"时,我们想问一些非常有目的的问题,关于我们软件环境中的数字,甚至是数字如何在语义上相互作用。好的。结论
让我们回顾一下我们应该问哪些问题:好的。
如果……好的。
检查代码文本中的独立清单常量基本值。对于这样一个值的每个实例,缓慢而深思熟虑地问每个问题。考虑一下你答案的力度。很多时候,答案不是黑白的,而是有误解的意义和目的、学习的速度和理解的速度。还需要了解它如何连接到周围的软件机器。好的。
最后,替换的答案是回答(在你的头脑中)读者的强弱来建立联系(例如"得到它")。他们越快理解意义和目的,你就越不具有"魔力"。好的。
结论:只有当魔法足够大,难以检测到由混乱引起的错误时,才用符号常量替换基本值。好的。好啊。
幻数是文件格式或协议交换开始时的字符序列。这个数字是一个健全的检查。
例子:打开任意一个GIF文件,您将在一开始看到:gif89。""是神奇的数字。
其他程序可以读取文件的前几个字符并正确识别gif。
危险在于随机二进制数据可以包含这些相同的字符。但这不太可能。
至于协议交换,您可以使用它快速识别正在传递给您的当前"消息"已损坏或无效。
神奇的数字仍然有用。
在编程中,"magic number"是一个应该被赋予符号名的值,但它被作为一个文本滑入代码中,通常位于多个位置。
出于同样的原因,spot(单点真理)是好的,这是不好的:如果您以后想更改这个常量,您必须搜索代码来查找每个实例。这也很糟糕,因为其他程序员可能不清楚这个数字代表什么,因此"魔法"。
人们有时通过将这些常量移动到单独的文件中作为配置来进一步消除幻数。这有时是有帮助的,但也会造成比其价值更大的复杂性。
一个还没有提到的使用魔法数字的问题…
如果你有很多,你有两个不同的目的,你使用魔法数字的概率是相当不错的,在那里值恰好是相同的。
当然,你需要改变这个值…只有一个目的。
幻数也可以是具有特殊的硬编码语义的数字。例如,我曾经看到一个系统,其中记录id>0被正常处理,0本身是"新记录",-1是"这是根",-99是"这是在根中创建的"。0和-99将导致WebService提供新的ID。
这有什么不好的地方,就是你要重用一个空间(有符号整数作为记录ID)来获得特殊的能力。也许您永远不想创建ID为0或负ID的记录,但即使不是这样,查看代码或数据库的每个人都可能会无意中发现这一点,并且一开始会感到困惑。毫无疑问,这些特殊的价值观没有很好的记录。
可以说,22、7、-12和620也算作神奇数字。;-)
我想这是我对你先前问题的回答。在编程中,幻数是一个嵌入的数字常量,它没有解释就出现了。如果它出现在两个不同的位置,则可能导致一个实例发生更改,而不是另一个实例。出于这两个原因,隔离和定义使用它们的地方之外的数值常数是很重要的。
用默认值初始化类顶部的变量怎么样?例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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是一个神奇的数字(根据支票样式)。对我来说,设置默认值是可以的。我不想这样做:
1 2 | private static final int DEFAULT_MAX_ROWS = 15000; private int maxRows = DEFAULT_MAX_ROWS; |
这会使阅读变得更困难吗?直到我安装了CheckStyles,我才考虑过这个问题。
我一直用"幻数"这个词来表示不同的含义,它是一个存储在数据结构中的模糊值,可以作为快速有效性检查进行验证。例如,GZIP文件包含0x1F8B08作为它们的前三个字节,Java类文件以0xCabeBabe等开始。
您经常会看到嵌入在文件格式中的幻数,因为文件可以随意发送,并且会丢失有关如何创建它们的元数据。然而,幻数有时也用于内存中的数据结构,比如ioctl()调用。
在处理文件或数据结构之前,对幻数进行快速检查,可以让人尽早发出错误信号,而不是一路拖来拖去可能漫长的处理过程,以宣布输入完全是胡说八道。
值得注意的是,有时您确实希望代码中包含不可配置的"硬编码"数字。在优化的逆平方根算法中有许多著名的算法,其中包括0x5F3759DF。
在我发现需要使用这种神奇数字的罕见情况下,我将它们设置为代码中的常量,并记录它们的使用原因、工作方式和来源。
返回变量呢?
在实现存储过程时,我特别发现它具有挑战性。
设想下一个存储过程(我知道,语法错误,只是为了显示一个示例):
1 | int procGetIdCompanyByName(string companyName); |
如果公司的ID存在于特定的表中,则返回该公司的ID。否则,它返回-1。不知何故,这是一个神奇的数字。到目前为止,我读到的一些建议说,我真的要做这样的设计:
1 | int procGetIdCompanyByName(string companyName, bool existsCompany); |
顺便问一下,如果公司不存在,它应该返回什么?好的:它会将existecompany设置为false,但也会返回-1。
安托弗的选择是做两个独立的功能:
1 2 | bool procCompanyExists(string companyName); int procGetIdCompanyByName(string companyName); |
所以第二个存储过程的前提条件是公司存在。
但我担心并发性,因为在这个系统中,一个公司可以由另一个用户创建。
顺便说一句,底线是:你怎么看待使用那种相对已知和安全的"神奇数字"来判断某件事不成功或某件事不存在?
@EED3SI9N:我甚至认为‘1’是一个神奇的数字。-)
与幻数相关的一个原则是,代码处理的每一个事实都应该声明一次。如果您在代码中使用了幻数(例如@marcio给出的密码长度示例),那么很容易就会重复这个事实,并且当您对这个事实的理解发生变化时,您会遇到维护问题。
将幻数提取为常量的另一个优点是可以清楚地记录业务信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } } } |