什么是神奇的数字?
为什么要避免?
有适合的情况吗?
幻数是代码中数字的直接使用.
例如,如果你有(在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这样的静态分析工具会检测代码中魔术数字的使用并建议重构.
Magic Number是一个硬编码值,可能会在以后更改,但因此很难更新.
例如,假设您有一个页面显示"您的订单"概述页面中的最后50个订单.50是这里的幻数,因为它不是通过标准或惯例设置的,它是由规范中列出的原因组成的数字.
现在,你所做的就是你在不同的地方有50个 - 你的SQL脚本(SELECT TOP 50 * FROM orders
),你的网站(你最近的50个订单),你的订单登录(for (i = 0; i < 50; i++)
)以及可能还有很多其他地方.
现在,当有人决定改变50到25时会发生什么?还是75?还是153?你现在必须在所有地方更换50,你很可能会错过它.查找/替换可能不起作用,因为50可能用于其他事情,盲目地用25替换50可能会产生一些其他不良副作用(即您的Session.Timeout = 50
呼叫,也设置为25,用户开始报告过于频繁的超时).
此外,代码可能很难理解,即" if a < 50 then bla
" - 如果您在复杂功能的中间遇到它,其他不熟悉代码的开发人员可能会问自己"WTF是50 ???"
这就是为什么最好在一个地方有这样的模糊和任意数字 - " const int NumOrdersToDisplay = 50
",因为这使代码更具可读性(" if a < NumOrdersToDisplay
",这也意味着你只需要在一个定义良好的地方改变它.
Magic Numbers适用的地方是通过标准定义的所有内容,即SmtpClient.DefaultPort = 25
或TCPPacketSize = whatever
(不确定是否标准化).此外,仅在1个函数中定义的所有内容都可以接受,但这取决于上下文.
您是否看过维基百科条目中的幻数?
它详细介绍了幻数引用的所有方法.这里引用魔术数作为一种糟糕的编程习惯
术语幻数也指在源代码中直接使用数字而不解释的错误编程实践.在大多数情况下,这会使程序更难以阅读,理解和维护.虽然大多数指南都会对数字0和1进行例外处理,但最好将代码中的所有其他数字定义为命名常量.
魔术:未知的语义
符号常量 - >提供正确的语义和正确的上下文供使用
语义:事物的意义或目的.
"创建一个常量,在意义之后命名,并用它替换数字." - 马丁福勒
首先,魔术数字不仅仅是数字.任何基本价值都可能是"神奇的".基本值是清单实体,例如整数,实数,双精度数,浮点数,日期,字符串,布尔值,字符等.问题不是数据类型,而是我们的代码文本中出现的值的"神奇"方面.
"魔术"是什么意思?确切地说:通过"魔术",我们打算在代码的上下文中指向值的语义(含义或目的); 它是未知的,不可知的,不清楚的或令人困惑的.这就是"魔术"的概念.当一个基本价值的语义或存在目的 - 在没有特殊帮助词(例如符号常数)的情况下从环绕语境中快速且容易地知道,清楚和理解(而不是混淆)时,它就不是魔术.
因此,我们通过测量代码阅读器从周围环境中了解,清楚和理解基本值的含义和目的的能力来识别魔术数字.读者越少知道,越不清楚,越混乱,基本价值就越"神奇".
有用的定义
混淆:导致(某人)变得迷惑或困惑.
困惑:导致(某人)变得困惑和困惑.
困惑:完全困惑; 非常困惑.
困惑:完全迷惑或困惑.
疑惑:无法理解; 困惑.
理解:感知(词语,语言或说话者)的意图.
含义:单词,文本,概念或动作的含义.
意思是:打算传达,指示或提及(某一特定事物或概念); 表示.
表示:表明.
指示:指示某事物的标志或信息.
表明:指出; 节目.
sign:对象,质量或事件,其存在或发生表明可能存在或发生其他事物.
基本
我们的魔术基本值有两个场景.只有第二个对程序员和代码至关重要:
一个唯一的基本值(例如数字),其含义是未知的,不可知的,不清楚的或混乱的.
上下文中的基本值(例如数字),但其含义仍然未知,不可知,不清楚或混淆.
"魔法"的总体依赖性是单独的基本值(例如数字)如何没有通常已知的语义(如Pi),但是具有本地已知的语义(例如您的程序),这在语境上并不完全清楚或者可能被滥用在好的或坏的情况下.
大多数编程语言的语义都不允许我们使用单独的基本值,除了(可能)作为数据(即数据表).当我们遇到"魔术数字"时,我们通常会在上下文中这样做.因此,答案
"我用符号常数替换这个神奇数字吗?"
是:
有点神奇,但并不完全"你能多快地在其背景下评估和理解数字的语义含义(它在那里的目的)?"
考虑到这一点,我们可以快速看到像Pi(3.14159)这样的数字在放置在适当的上下文中时是不是一个"幻数"(例如2 x 3.14159 x radius或2*Pi*r).这里,数字3.14159是精神上认可的Pi,没有符号常数标识符.
尽管如此,由于数字的长度和复杂性,我们通常用像Pi这样的符号常量标识符替换3.14159.Pi的长度和复杂性方面(加上对精度的需求)通常意味着符号标识符或常量不易出错.将"Pi"识别为名称只是一个简单方便的奖励,但不是获得常数的主要原因.
同时:回到牧场抛开像Pi这样的常见常量,让我们主要关注具有特殊含义的数字,但这些意义仅限于我们软件系统的范围.这样的数字可能是"2"(作为基本整数值).
如果我自己使用数字2,我的第一个问题可能是:"2"是什么意思?"2"本身的含义本身是未知的,没有上下文是不可知的,使其使用不清楚和混乱.即使我们的软件中只有"2"也不会因为语言语义而发生,我们确实希望看到"2"本身不带有特殊的语义或明显的目的.
让我们把单独的"2"放在上下文中:padding := 2
上下文是"GUI容器".在这种情况下,2的含义(作为像素或其他图形单元)为我们提供了对其语义(意义和目的)的快速猜测.我们可能会在这里停下来说2在这种情况下是可以的,我们不需要知道任何其他事情.然而,也许在我们的软件世界中,这不是整个故事.还有更多内容,但"padding = 2"作为上下文无法揭示它.
让我们进一步假设我们程序中的2像素填充是整个系统中的"default_padding"变种.因此,编写指令padding = 2
不够好."违约"的概念没有透露.只有当我写作:padding = default_padding
作为一个背景,然后在其他地方:default_padding = 2
我是否在我们的系统中完全实现2的更好和更充分的意义(语义和目的).
上面的例子非常好,因为"2"本身可以是任何东西.只有当我们将理解的范围和领域限制为"我的程序",其中2是"我的程序" default_padding
的GUI UX部分时,我们才能在其适当的上下文中理解"2".这里"2"是一个"魔术"数字,它default_padding
在"我的程序"的GUI UX的上下文中被分解为符号常量,以便default_padding
在封闭代码的更大上下文中快速理解它.
因此,任何基本价值,其含义(语义和目的)都不能被充分和快速地理解,是代替基本价值(例如幻数)的符号常数的良好候选者.
走得更远规模上的数字也可能具有语义.例如,假装我们正在制作一个D&D游戏,我们有一个怪物的概念.我们的怪物对象有一个叫做life_force
整数的特征.这些数字具有不可知或不清楚的含义,无法提供意义.因此,我们开始任意说:
full_life_force:INTEGER = 10 - 非常活跃(并且没有受伤)
minimum_life_force:INTEGER = 1 - 勉强活着(非常受伤)
死:INTEGER = 0 - 死了
亡灵:INTEGER = -1 - 最小不死生物(几乎死亡)
僵尸:INTEGER = -10 - 最大不死生物(非常不死生物)
从上面的象征性常数开始,我们开始在我们的D&D游戏中了解我们的怪物的活力,死亡和"不死"(以及可能的后果或后果).如果没有这些词(符号常量),我们只剩下来自的数字-10 .. 10
.如果游戏的不同部分依赖于该范围的数字对于各种操作(例如attack_elves
或等)的依赖性,那么只有没有文字的范围会让我们处于可能非常困惑的地方并且可能在我们的游戏中出现错误seek_magic_healing_potion
.
因此,在搜索和考虑替换"魔术数字"时,我们想要询问关于我们软件环境中的数字的非常充满目的的问题,甚至是数字如何在语义上相互作用.
结论让我们回顾一下我们应该问的问题:
如果......你可能有一个神奇的数字
基本价值在您的软件世界中是否具有特殊含义或目的?
即使在适当的背景下,特殊意义或目的是否可能是未知的,不可知的,不清楚的或混乱的?
在错误的背景下,是否可以正确使用适当的基本价值并带来不良后果?
在正确的背景下,是否可以正确使用不正确的基本价值并带来不良后果?
基本值是否与特定上下文中的其他基本值具有语义或目的关系?
我们的代码中的多个地方是否存在基本值,每个地方都有不同的语义,从而导致我们的读者感到困惑?
检查代码文本中的独立清单常量基本值.每个问题都要缓慢而周密地询问每个问题.考虑你的答案的力量.很多时候,答案不是黑白分明,而是有误解的意义和目的,学习速度和理解速度.还需要了解它如何连接到它周围的软件机器.
最后,替换的答案是回答(在你的脑海中)读者的力量或弱点的连接(例如"得到它").他们理解意义和目的的速度越快,你所拥有的"神奇"就越少.
结论:只有当魔法大到足以导致难以发现混乱引起的错误时,才用符号常量替换基本值.
幻数是文件格式或协议交换开始时的字符序列.这个号码可作为健全检查.
示例:打开任何GIF文件,您将在一开始就看到:GIF89."GIF89"是神奇的数字.
其他程序可以读取文件的前几个字符并正确识别GIF.
危险在于随机二进制数据可以包含这些相同的字符.但这不太可能.
至于协议交换,您可以使用它来快速识别传递给您的当前"消息"是否已损坏或无效.
魔术数字仍然有用.
在编程中,"幻数"是一个应该赋予符号名称的值,而是作为文字滑入代码中,通常在不止一个地方.
这与SPOT(单点真相)相同的原因很不好:如果你想在以后更改此常量,则必须搜索代码以查找每个实例.这也很糟糕,因为其他程序员可能不清楚这个数字代表什么,因此是"魔术".
人们有时会通过将这些常量移动到单独的文件中作为配置来进一步消除幻数.这有时很有用,但也可能造成比它更值得的复杂性.
幻数也可以是具有特殊硬编码语义的数字.例如,我曾经看过一个系统,其中记录ID> 0被正常处理,0本身是"新记录",-1是"这是根",-99是"这是在根中创建的".0和-99会导致WebService提供新ID.
这有什么不好的是你正在重用一个特殊能力的空间(记录ID的有符号整数).也许你永远不会想要创建ID为0或带有负ID的记录,但即使不是这样,每个看过代码或数据库的人都可能偶然发现并且最初会感到困惑.不言而喻,这些特殊的价值观没有得到很好的记录.
可以说,22,7,-12和620也算作魔术数字.;-)
使用魔术数字时未提及的问题......
如果你有很多这样的机会,那么你有两个不同的目的就是使用魔术数字,而这些数值碰巧是相同的.
然后,当然,您需要更改值...仅用于一个目的.