Mar*_*iar 549
幻数是代码中数字的直接使用.
例如,如果你有(在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这样的静态分析工具会检测代码中魔术数字的使用并建议重构.
Mic*_*tum 142
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个函数中定义的所有内容都可以接受,但这取决于上下文.
Lar*_*rry 25
魔术:未知的语义
符号常量 - >提供正确的语义和正确的上下文供使用
语义:事物的意义或目的.
"创建一个常量,在意义之后命名,并用它替换数字." - 马丁福勒
首先,魔术数字不仅仅是数字.任何基本价值都可能是"神奇的".基本值是清单实体,例如整数,实数,双精度数,浮点数,日期,字符串,布尔值,字符等.问题不是数据类型,而是我们的代码文本中出现的值的"神奇"方面.
"魔术"是什么意思?确切地说:通过"魔术",我们打算在代码的上下文中指向值的语义(含义或目的); 它是未知的,不可知的,不清楚的或令人困惑的.这就是"魔术"的概念.当一个基本价值的语义或存在目的 - 在没有特殊帮助词(例如符号常数)的情况下从环绕语境中快速且容易地知道,清楚和理解(而不是混淆)时,它就不是魔术.
因此,我们通过测量代码阅读器从周围环境中了解,清楚和理解基本值的含义和目的的能力来识别魔术数字.读者越少知道,越不清楚,越混乱,基本价值就越"神奇".
我们的魔术基本值有两个场景.只有第二个对程序员和代码至关重要:
"魔法"的总体依赖性是单独的基本值(例如数字)如何没有通常已知的语义(如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整数的特征.这些数字具有不可知或不清楚的含义,无法提供意义.因此,我们开始任意说:
从上面的象征性常数开始,我们开始在我们的D&D游戏中了解我们的怪物的活力,死亡和"不死"(以及可能的后果或后果).如果没有这些词(符号常量),我们只剩下来自的数字-10 .. 10.如果游戏的不同部分依赖于该范围的数字对于各种操作(例如attack_elves或等)的依赖性,那么只有没有文字的范围会让我们处于可能非常困惑的地方并且可能在我们的游戏中出现错误seek_magic_healing_potion.
因此,在搜索和考虑替换"魔术数字"时,我们想要询问关于我们软件环境中的数字的非常充满目的的问题,甚至是数字如何在语义上相互作用.
让我们回顾一下我们应该问的问题:
如果......你可能有一个神奇的数字
检查代码文本中的独立清单常量基本值.每个问题都要缓慢而周密地询问每个问题.考虑你的答案的力量.很多时候,答案不是黑白分明,而是有误解的意义和目的,学习速度和理解速度.还需要了解它如何连接到它周围的软件机器.
最后,替换的答案是回答(在你的脑海中)读者的力量或弱点的连接(例如"得到它").他们理解意义和目的的速度越快,你所拥有的"神奇"就越少.
结论:只有当魔法大到足以导致难以发现混乱引起的错误时,才用符号常量替换基本值.
Bri*_*ndy 19
幻数是文件格式或协议交换开始时的字符序列.这个号码可作为健全检查.
示例:打开任何GIF文件,您将在一开始就看到:GIF89."GIF89"是神奇的数字.
其他程序可以读取文件的前几个字符并正确识别GIF.
危险在于随机二进制数据可以包含这些相同的字符.但这不太可能.
至于协议交换,您可以使用它来快速识别传递给您的当前"消息"是否已损坏或无效.
魔术数字仍然有用.
Nic*_*ack 11
在编程中,"幻数"是一个应该赋予符号名称的值,而是作为文字滑入代码中,通常在不止一个地方.
这与SPOT(单点真相)相同的原因很不好:如果你想在以后更改此常量,则必须搜索代码以查找每个实例.这也很糟糕,因为其他程序员可能不清楚这个数字代表什么,因此是"魔术".
人们有时会通过将这些常量移动到单独的文件中作为配置来进一步消除幻数.这有时很有用,但也可能造成比它更值得的复杂性.
我总是以不同的方式使用术语“幻数”,作为存储在数据结构中的模糊值,可以通过快速有效性检查进行验证。例如,gzip 文件包含 0x1f8b08 作为其前三个字节,Java 类文件以 0xcafebabe 开头,等等。
您经常会看到文件格式中嵌入了幻数,因为文件可能会相当混杂地发送,并且会丢失有关其创建方式的任何元数据。然而,幻数有时也用于内存中的数据结构,例如 ioctl() 调用。
在处理文件或数据结构之前快速检查幻数可以让人们及早发出错误信号,而不是一路拖着可能冗长的处理来宣布输入完全是胡言乱语。