当前位置:  开发笔记 > 前端 > 正文

什么是神奇的数字,为什么它不好?

如何解决《什么是神奇的数字,为什么它不好?》经验,为你挑选了8个好方法。

什么是神奇的数字?

为什么要避免?

有适合的情况吗?



1> Marcio Aguia..:

幻数是代码中数字的直接使用.

例如,如果你有(在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,CharacterMath类.

PS:像FindBugs和PMD这样的静态分析工具会检测代码中魔术数字的使用并建议重构.


0和1是此规则的例外.
仅仅因为一个神奇的数字永远不会改变并不意味着它不应该被一个常数所取代.我的代码充满了全局常量,如HzPerMHz和msecPerSecond.这些永远不会改变,但它们使意义更加清晰,并提供一些防止打字错误的保护.
@MarcusJ你错了.这不是一个意见问题,而是许多程序员辛苦赚来的经验.在过去的40年编程中,我无法告诉你,我曾经诅咒过一位没有定义常数的程序员,所以我只发现了一个数字的直接使用,这在代码维护期间需要被理解,埋在很多代码中的某个地方,通过定义这样一个常数可以明确其含义.任何其他高级程序员都会在这条线上有多个恐怖故事.
@Jonathan Parker,除非他们不是(`TRUE` /`FALSE`)
@Kirill:如果你期望"百分百"的定义改变,那么是的.一个更好的方法是将变量从它所代表的变量,即公共静态最终MAX_DOWNLOAD_PERCENTAGE = 100.尽管即使这样也没有意义,因为"100%"是非常明确的定义.另一方面,密码最多可以包含7个字符的事实并不是全局定义的,实际上是不同的,因此它是变量的候选者.
@JonathanParker不同意,取决于具体情况.
......养成定义常数的习惯是必不可少的,即使在琐碎的方法中,这些数字似乎是显而易见的.为什么?因为将来可能会添加该方法.起初看起来很明显,现在在许多代码行中.至于是否是精神充沛,AFAIK所有体面的现代IDE都可以轻松找到常量变量的值.通常可以将鼠标悬停在变量的任何使用上.甚至在白天,当我们没有这样的细节时,从长远来看,这非常非常值得.
其他好的自然常量包括ONE_MINUTE = 60,ONE_HOUR = 3600,ONE_DAY = 86400(大多数程序员将使用纪元时间)...或者
@Sergey _"怎么样的东西:connection.setTimeout(50); 50应该是一个常量吗?似乎很清楚,50是一个连接超时"_ - 无论是单个还是多个调用它都是一个神奇的数字.为什么50?为什么不是51?您可能需要更改它,因为您正在部署到需要不同超时的环境.我宁愿改变常量而不是通过代码搜索.
我应该写:`public static final HUNDRED_PERCENTS = 100;`?
“ 1”和“ 0”仍然可以是幻数,仅取决于上下文。检查`if someArray.length === 0`的目的很明显,您正在检查它是否为空,这不被视为幻数。但是,如果您执行“ createSomething(1)”,那将是一个神奇的数字。无法从上下文中获得1的含义-特别是当“ 1”充当布尔值时。如果您使用的是“ true”或“ false”,则很明显此参数为开或关。
我会说那段代码不太可读,为什么你不想在不改变的情况下去寻找MAX_PASSWORD_SIZE的定义呢?你真的只是让它更加精神化.
那样的事情:`connection.setTimeout(50);`应该`50`在这里是常数吗?似乎很清楚,"50"是连接超时
@Sankalp MagicNumber:默认情况下,-1,0,1和2不被视为幻数.[参考文献](http://checkstyle.sourceforge.net/config_coding.html)
直接使用魔术数字本身并不是件坏事.很多数学公式都包含魔术数字,真的很多**.计算机和软件建立在同一数学上.引用可能在将来使用变量重构的数字*更有用.因此,根据我的观点,这个答案过于简单了.尝试以这种方式实现简单的物理,你会知道我的意思.
在.NET世界中,FxCop和Visual Studio 2008代码度量标准应该为您指出这些.
@BrendanLong它并不总是必须是`TRUE` /`FALSE`作为例外.示例:`if(array.length == 0)`(或`<1`)

2> Michael Stum..:

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 = 25TCPPacketSize = whatever(不确定是否标准化).此外,仅在1个函数中定义的所有内容都可以接受,但这取决于上下文.


即使它不能改变它仍然是一个坏主意,因为它不清楚发生了什么.
这并不总是不清楚.`SmtpClient.DefaultPort = 25`可能比'SmtpClient.DefaultPort = DEFAULT_SMTP_PORT`清除*er*.
@immibis我想假设绝对没有其他代码使用DEFAULT_SMTP_PORT的概念.如果更改了该应用程序的默认SMTP端口,则需要在多个位置更新,从而导致出现不一致的可能性.
在那个例子中,我希望代码使用SmtpClient.DefaultPort,而不是25.所以你只需要在一个地方改变它.并且端口号可能保持不变,它不是随机幻数,而是由"IANA"分配的数字.

3> somas1..:

您是否看过维基百科条目中的幻数?

它详细介绍了幻数引用的所有方法.这里引用魔术数作为一种糟糕的编程习惯

术语幻数也指在源代码中直接使用数字而不解释的错误编程实践.在大多数情况下,这会使程序更难以阅读,理解和维护.虽然大多数指南都会对数字0和1进行例外处理,但最好将代码中的所有其他数字定义为命名常量.


RTFW的好例子:)

4> Larry..:
幻数与比 符号常数:何时更换?

魔术:未知的语义

符号常量 - >提供正确的语义和正确的上下文供使用

语义:事物的意义或目的.

"创建一个常量,在意义之后命名,并用它替换数字." - 马丁福勒

首先,魔术数字不仅仅是数字.任何基本价值都可能是"神奇的".基本值是清单实体,例如整数,实数,双精度数,浮点数,日期,字符串,布尔值,字符等.问题不是数据类型,而是我们的代码文本中出现的值的"神奇"方面.

"魔术"是什么意思?确切地说:通过"魔术",我们打算在代码的上下文中指向值的语义(含义或目的); 它是未知的,不可知的,不清楚的或令人困惑的.这就是"魔术"的概念.当一个基本价值的语义或存在目的 - 在没有特殊帮助词(例如符号常数)的情况下从环绕语境中快速且容易地知道,清楚和理解(而不是混淆)时,它就不是魔术.

因此,我们通过测量代码阅读器从周围环境中了解,清楚和理解基本值的含义和目的的能力来识别魔术数字.读者越少知道,越不清楚,越混乱,基本价值就越"神奇".

有用的定义

混淆:导致(某人)变得迷惑或困惑.

困惑:导致(某人)变得困惑和困惑.

困惑:完全困惑; 非常困惑.

困惑:完全迷惑或困惑.

疑惑:无法理解; 困惑.

理解:感知(词语,语言或说话者)的意图.

含义:单词,文本,概念或动作的含义.

意思是:打算传达,指示或提及(某一特定事物或概念); 表示.

表示:表明.

指示:指示某事物的标志或信息.

表明:指出; 节目.

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.

因此,在搜索和考虑替换"魔术数字"时,我们想要询问关于我们软件环境中的数字的非常充满目的的问题,甚至是数字如何在语义上相互作用.

结论

让我们回顾一下我们应该问的问题:

如果......你可能有一个神奇的数字

    基本价值在您的软件世界中是否具有特殊含义或目的?

    即使在适当的背景下,特殊意义或目的是否可能是未知的,不可知的,不清楚的或混乱的?

    在错误的背景下,是否可以正确使用适当的基本价值并带来不良后果?

    在正确的背景下,是否可以正确使用不正确的基本价值并带来不良后果?

    基本值是否与特定上下文中的其他基本值具有语义或目的关系?

    我们的代码中的多个地方是否存在基本值,每个地方都有不同的语义,从而导致我们的读者感到困惑?

检查代码文本中的独立清单常量基本值.每个问题都要缓慢而周密地询问每个问题.考虑你的答案的力量.很多时候,答案不是黑白分明,而是有误解的意义和目的,学习速度和理解速度.还需要了解它如何连接到它周围的软件机器.

最后,替换的答案是回答(在你的脑海中)读者的力量或弱点的连接(例如"得到它").他们理解意义和目的的速度越快,你所拥有的"神奇"就越少.

结论:只有当魔法大到足以导致难以发现混乱引起的错误时,才用符号常量替换基本值.



5> Brian R. Bon..:

幻数是文件格式或协议交换开始时的字符序列.这个号码可作为健全检查.

示例:打开任何GIF文件,您将在一开始就看到:GIF89."GIF89"是神奇的数字.

其他程序可以读取文件的前几个字符并正确识别GIF.

危险在于随机二进制数据可以包含这些相同的字符.但这不太可能.

至于协议交换,您可以使用它来快速识别传递给您的当前"消息"是否已损坏或无效.

魔术数字仍然有用.


我不认为这是他所指的神奇数字
知道魔术数字不仅仅指代代码问题仍然非常有用.-亚当
也许你应该删除你添加的"文件格式"和"网络"标签,因为他显然不是在谈论那些魔术数字.
也无法确定他在寻找哪一个.由于我是第一篇文章,我无法猜出他正在寻找哪一个.
如果主题为:"源代码中的幻数是多少",则标签不应该在那里.但他没有说明这一点.因此,获取我的额外信息是好的.我认为Kyle,Landon和Marcio都错了.

6> Nick Retalla..:

在编程中,"幻数"是一个应该赋予符号名称的值,而是作为文字滑入代码中,通常在不止一个地方.

这与SPOT(单点真相)相同的原因很不好:如果你想在以后更改此常量,则必须搜索代码以查找每个实例.这也很糟糕,因为其他程序员可能不清楚这个数字代表什么,因此是"魔术".

人们有时会通过将这些常量移动到单独的文件中作为配置来进一步消除幻数.这有时很有用,但也可能造成比它更值得的复杂性.


Marcio:当你做"const int EIGHT = 8"之类的事情时 然后需求发生变化,最终得到"const int EIGHT = 9;"
对不起,但这只是一个错误命名的例子,或者常量的基本用法.

7> Sören Kuklau..:

幻数也可以是具有特殊硬编码语义的数字.例如,我曾经看过一个系统,其中记录ID> 0被正常处理,0本身是"新记录",-1是"这是根",-99是"这是在根中创建的".0和-99会导致WebService提供新ID.

这有什么不好的是你正在重用一个特殊能力的空间(记录ID的有符号整数).也许你永远不会想要创建ID为0或带有负ID的记录,但即使不是这样,每个看过代码或数据库的人都可能偶然发现并且最初会感到困惑.不言而喻,这些特殊的价值观没有得到很好的记录.

可以说,22,7,-12和620也算作魔术数字.;-)



8> 小智..:

使用魔术数字时未提及的问题......

如果你有很多这样的机会,那么你有两个不同的目的就是使用魔术数字,而这些数值碰巧是相同的.

然后,当然,您需要更改值...仅用于一个目的.

推荐阅读
k78283381
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有