所有的魔术数字都一样吗?


77

在最近的项目中,我需要将字节转换为千字节 kibibyte。代码很简单:

var kBval = byteVal / 1024;

编写完之后,我就完成了其余的功能并继续进行。

但是后来,我开始怀疑我是否在代码中嵌入了一个幻数。我的一部分说这很好,因为该数字是固定的常数,应该很容易理解。但是我的另一部分认为,如果将其包裹在一个已定义的常量(如)中,它将非常清晰BYTES_PER_KBYTE

那么,众所周知的常数是否真的那么神奇呢?


相关问题:

数字什么时候是魔数?被认为是在代码中的每个号码的“幻数”?-相似,但问题比我要问的要广泛得多。我的问题集中在那些问题中没有解决的众所周知的常数上。

消除魔术数字:什么时候该说“不”?也是相关的,但专注于重构,而不是常数是否为幻数。


17
实际上,我从事的项目是他们创建了诸如的常量FOUR_HUNDRED_FOUR = 404。我在另一个项目中工作过,他们热衷于使用常量字符串而不是文字,因此他们在代码中有数十行,看起来像是,DATABASE = "database"
Rob

82
一定要使用1024,因为否则您的开发团队将全神贯注于它是“千字节”还是“千字节”。
Steven Burnap 2014年

6
您可能会认为1024是kibi,#define KIBI而1024 MEBI是1024 * 1024…
ysdx 2014年

6
@Rob Y:听起来像是老的Fortran程序员。因为那种编程语言迫使程序员这样做。是的,在那里您将看到常量,ZERO=0, ONE=1, TWO=2并且当程序移植到其他语言时(或者程序员在切换语言时不会改变行为),您也会在那里看到它,并且您必须祈祷永远不要有人将其更改为ONE=2……
Holger 2014年

4
@NoctisSkytower我的团队更喜欢使用显式除法语句而不是移位运算符,因为我们使用的是多种语言,而且这些语言之间的实现可能不一致。同样,负值在按位移位时也不一致。尽管我们不一定有负字节值,但我们转换其他度量单位时肯定有负值。

Answers:


103

并非所有魔术数字都是相同的。

我认为在那种情况下,该常数是可以的。魔幻数字的问题在于它们何时是魔幻的,即不清楚其起源是什么,其值为何为真,或该值是否正确。

在BYTES_PER_KBYTE之后隐藏1024,也意味着您看不到它是否正确。

我希望有人会立即知道为什么该值为1024。另一方面,如果要将字节转换为兆字节,我将定义常量BYTES_PER_MBYTE或类似的常量,因为常量1,048,576不太明显,以至于它的1024 ^ 2或甚至是正确的。

对于仅在一个地方使用的由要求或标准规定的值也是如此。我发现只需在相关信息的注释处加上常量即可,比在其他地方定义常量和将两个部分都逐一删除更容易处理,例如:

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

我发现比

SomeTest = DataSample < SOME_THRESHOLD_VALUE

SOME_THRESHOLD_VALUE我认为,只有在多个地方使用时,才有必要定义一个常数。


67
“与幻数的问题是,当他们的魔法” -这是此类这一概念的解释辉煌!我是认真的!仅对该句子+1。
约尔格W¯¯米塔格

20
这是我刚想到的:“问题不是数字,而是魔术。”
约尔格W¯¯米塔格

10
1024对谁来说是显而易见的?这不是每个魔术数字的正当理由吗?之所以使用所有魔术数字,是因为对于任何编写它们的人来说,它们都是显而易见的。9.8也不明显吗?对我来说,这很明显是地球上的重力加速度,但尽管如此,我还是会创建一个常数,因为对我而言,显而易见的东西可能对其他人而言并不明显。
图兰斯·科尔多瓦

15
不。像您的“更好”示例中的评论一样,它是一个危险的大信号。该代码甚至没有通过当时编写它的人的可读性测试。我举一个例子。e^i*pi = -1比更加明确(更好)2.718^i*3.142 = -1。变量很重要,它们不仅适用于通用代码。编写代码时要先阅读,然后再编译。此外,规格也会发生变化(很多)。虽然1024可能不应该在配置中,但3.5听起来应该是应该的。
内森·库珀

51
我也不会为1024 ^ 2使用常量;1024*1024请!
Lightness Races in Orbit

44

关于魔术数字,我要问两个问题。

这个号码有名字吗?

名称很有用,因为我们可以阅读名称并了解其背后数字的用途。如果名称比替换的数字更容易理解常量名称简洁,则命名常量可以提高可读性。

显然,诸如pi,e等常数。有有意义的名字。诸如1024的值可以是,BYTES_PER_KB但我也希望任何开发人员都知道1024的含义。源代码的目标读者是专业程序员,他们应该具有了解两者的各种功效以及为什么使用它们的背景。

是否在多个位置使用?

名称是常量的一种优势,而另一种则是可重用性。如果某个值可能会更改,则可以在一个地方进行更改,而无需在多个位置进行查找。

你的问题

对于您的问题,我将按原样使用该数字。

名称:该号码有一个名称,但实际上并没有什么用。它不代表任何要求文档中指定的数学常数或值。

位置:即使在多个位置使用,它也永远不会改变,从而抵消了这种好处。


1
使用常量而不是魔术数字的原因不仅是因为所述数字会发生变化,还在于可读性和自我证明。
图兰斯·科尔多瓦

4
@ user61852:命名常量并不总是更具可读性。他们经常是,但并非总是如此。
whatsisname 2014年

2
我个人使用这两个问题来代替:“此值在程序的生命周期中会改变吗?” 和“我希望使用此软件的开发人员是否理解此数字的用途?”
Steven Burnap 2014年

4
您是说Y2K问题吗?我不确定这里是否有意义。是的,有很多类似“ date-1900”的代码,但是在该代码中,麻烦不是神奇的数字“ 1900”。
Steven Burnap 2014年

1
提到这一点可能会受益,因为某些“明显的”数字(肯定是1024)是一个数字,使得其他开发人员很可能自发将它们写为数字,即使有人为它们定义了命名常量也是如此。如果我还不知道存在一个常量,那么我很可能甚至不会考虑在源代码中搜索1024 ,如果我需要在字节数转换中使用1024。
海德2014年

27

这句话

问题不是数字,而是魔术。

正如JörgW Mittag 所说,这个问题回答得很好。

在某些情况下,某些数字根本不是神奇的。在问题提供的示例中,度量单位由变量名称指定,并且正在执行的操作非常清楚。

1024并不是魔术,因为上下文非常清楚地表明它是用于转换的适当的恒定值。

同样,以下示例:

var numDays = numHours / 24; 

同样清晰,也不是不可思议的,因为众所周知一天中有24小时。


21
但是...但是... 24可以改变!地球正在减慢其自转速度,最终将有25个小时!(当然,到那时我们都会死了,这使该软件的维护成为别人的问题)

14
在Mars上部署您的软件后会发生什么您应该注入该常数...
durron597

8
@ durron597:如果你的程序运行足够长的土放缓在这段时间。您不应该注入一个常量,而应该接受一个时间戳(现在是默认值)并返回时间戳落下一天中的小时数的函数;-)
Steve Jessop 2014年

13
您需要学习YAGNI。
whatsisname 2014年

3
@ durron597当您的计时软件部署在火星上时,没有什么特别的事情,因为按照惯例,火星的天数是24小时,但是每个小时比地球上的时间长2.7%。当然,地球的恒星日和地球的太阳日都不是24小时(确切的数字在同一页上),因此您24 无论如何都不能使用就像伊兹卡塔(Izkata)所述,leap秒受伤。也许24在火星上实际使用常量比在地球上使用运气好!
2014年

16

其他张贴者提到转换是“显而易见的”,但我不同意。此时的原始问题包括:

千字节 kibibytes

所以我已经知道作者是或感到困惑。维基百科页面增加了混乱:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

因此,“千字节”可以用来表示1000和1024的因数,唯一的区别是'k'的大小写。最重要的是,1024可以表示千字节(JEDEC)或千字节(IEC)。为什么不使用具有有意义名称的常量彻底消除所有这些混乱呢?顺便说一句,该线程经常使用“ BYTES_PER_KBYTE”,这也同样含糊不清。KBYTE:是KIBIBYTE还是KILOBYTE?我宁愿忽略JEDEC,而拥有BYTES_PER_KILOBYTE = 1000and BYTES_PER_KIBIBYTE = 1024。没有更多的混乱。

像我这样的人以及其他许多人对命名魔术数字有“好战”(在这里引用评论者)的观点,其原因全在于记录您打算做什么,以及消除歧义。您实际上选择了一个导致很多混乱的单元。

如果我看到:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

然后,作者的意图立即显而易见,并且没有歧义。我可以在几秒钟内检查常数(即使它在另一个文件中),因此即使它不是“即时”的,也足够接近即时。

最后,当您编写它时可能会很明显,但是稍后再使用它时,它会变得不那么明显,而当其他人对其进行编辑时,它甚至会变得不那么明显。常数需要10秒;调试单元问题可能需要半小时或更长时间(代码不会突然出现在您面前,并告诉您单元是错误的,您将必须自己做数学运算才能弄清楚,并且您可能会在检查单位之前搜寻10种不同的途径)。


2
好的反击答案。如果您考虑个人团队文化,那会更好。如果您相信我的SE资料,那么我已经足够老,可以早于那些特定的标准。因此,唯一的困惑来自“当前(非)标准术语是什么?” 假设我与一组具有相同(非)难度的恐龙同伴一起工作,您可能会很放心。

@ GlenH7:恕我直言,基于2的幂的单元应该保留用于存储,因为它是按2的幂的块分配的。最小分配大小为4096字节,是否有一个单位可以容纳256个最小尺寸的文件,或者容纳244.140625这样的文件所需的存储量更有意义?就个人而言,我认为硬盘制造商兆字节与其他兆字节之间的差异类似于电视机对角线英寸与实际对角线英寸之间的差异。
supercat 2014年

@Ryan:对于这种特定情况,我宁愿采用标准单位-KB为1000字节或代码错误,而1024字节为KiB或代码错误。这是我们要克服“单位模棱两可”的问题的唯一方法。不同的人以不同的方式定义“魔术常数”(例如KB)不会有帮助。
布伦丹2014年

11

将名称定义为指的是数值,这意味着只要在使用该名称的位置需要一个不同的值,便可能全部需要该值。它还倾向于建议更改分配给名称的数字值是更改值的合法方法。这种含义在为真时可能有用,而在为假时则很危险。

两个不同的地方使用特定的文字值(例如1024)这一事实将很弱地表明,提示程序员更改一个地方的更改在某种程度上可能会激发程序员想要更改其他地方,但是隐含的含义要弱得多。如果程序员为该常量分配了名称。

诸如此类的主要危险#define BYTES_PER_KBYTE 1024是,它可能printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));会给遇到的人建议使代码使用数千个字节的安全方法是更改#define语句。但是,如果其他一些不相关的代码接收到对象的大小(以千字节为单位)并在为其分配缓冲区时使用该常量,则这种更改可能是灾难性的。

使用#define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024#define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024为常数1024服务的每个不同目的分配不同的名称可能是合理的,但是这将导致许多标识符被定义和使用一次。此外,在许多情况下,最容易理解如果在哪里看到了代码,该值意味着什么,并且最容易弄清楚如果看到其中使用的任何常量的值,则意味着代码在哪里。如果数字文字仅出于特定目的使用一次,那么与在一个地方分配标签并在其他地方使用其值相比,在使用该文字的地方书写文字通常会产生更易理解的代码。


7

我倾向于仅使用数字,但是我认为还没有提出一个重要的问题:相同的数字在不同的上下文中可能意味着不同的事情,这会使重构变得复杂。

每个MiB的KiB数也为1024。假设我们也使用1024来表示某个位置或多个位置的计算,现在我们需要更改为计算GiB。更改常数比全局查找/替换要容易,在全局查找/替换中,您可能在某些地方意外更改了错误的地方,或者在其他地方错过了错误的地方。

或者它甚至可能是由懒惰的程序员引入的掩码,需要一天更新。

这是一个人为的示例,但是在某些代码库中,这可能会在重构或更新新要求时引起问题。但是对于这种特殊情况,我不会认为素数确实是不好的形式,尤其是如果您可以将计算包含在重用方法中,我可能会自己做,但认为常数更“正确”。

但是,如果您确实使用命名常量,正如supercat所说,考虑上下文是否也很重要以及是否需要多个名称非常重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.