代码中的每个数字都被视为“幻数”吗?


21

因此,我们将作为参数发送给方法的代码中的每个数字都视为幻数?对我来说,不应该。我想,如果让某个数字代表用户名的最小长度,并且我们开始在代码中使用“ 6” ...那么,是的,我们遇到了维护问题,这里的“ 6”是一个魔术数字....但是如果我们正在调用一个方法,该方法的参数之一例如接受整数作为集合的第ith个成员,然后我们将“ 0”传递给该方法调用,那么在这种情况下,我认为“ 0”不是魔术数。你怎么看?


4
在您的示例中,0代表什么?
亚伦·库尔扎尔斯

2
在您说明的情况下,“ 0”没有任何神奇的属性。
TulainsCórdova13年

4
除了0,1和42之外的所有东西都是魔术
Mawg 2014年

Answers:


43

如果数字的含义在上下文中非常清楚,那么我认为这不是“魔术数字”问题。

示例:假设您尝试获取字符串的子字符串,从开始到某些令牌,并且代码看起来像这样(虚构语言和库):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

在这种情况下,数字0的含义很清楚。我想您可以定义START_OF_SUBSTRING并将其设置为0,但是在这种情况下,我认为这会过大(尽管如果您知道子字符串的开头可能不是0,但这将是正确的方法,但这取决于您的情况)。

另一个示例可能是您是否要确定数字是偶数还是奇数。写作:

isEven := x % 2;

不像:

TWO := 2;
isEven := x % TWO;

测试负数为

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

对我也很奇怪,我宁愿看到

isNegativeInt := i <= -1;

6
再举一个例子,在代码中,您明确地在一个圆上使用度数360,在理解大多数人会知道这意味着什么的情况下,使用一个数字来标记一个完整的旋转是公平的。是提供常数就不会受到伤害的情况)
KChaloux

11
KChaloux:如果可以的话,我会为您的评论-1。360是个魔术数字。如果360恰好是另一个常数的值,那么360有2组是不相关且不可区分的。Junior出现了,去“那是一个神奇的数字”,全局搜索并将360替换为“ Degrees_in_Circle”,运行所有单元测试和回归测试,全部通过-提供代码修复功能。现在给狗早餐编码,我们都知道短时间后会发生什么。……
mattnz 2013年

4
@mattnz:希望这种大规模的代码更改能够在投入生产之前很早就被发现(希望在初级的代码审查期间,如果他们是初级的话)。我认为在这种情况下愿意这样做的人也可能会0在我的子字符串示例的情况下代替。在这种情况下,这可能是它们可能造成的最少损失。自从我进行几何计算的编码以来已经有很长时间了,但是通常,值15、30、45、60、90、180、360是可以接受的常数。我从未见过有人定义FIFTEEN_DEGREES...
FrustratedWithFormsDesigner 2013年

5
@KChaloux如果从度数转换为弧度,则该示例实际上可能会崩溃。到360,表示1个完整旋转。由于同一值有多种表示形式,因此应将其拔出。特别是考虑到360PI可能看起来与2PI相同(180旋转,但最终仍指向同一方向),或者360旋转与1旋转相同,但是副作用可能有所不同。
克里斯,

14
那里有些稻草人,TWO和MINUS_ONE完全不好,因为用文本中的呈现替换幻数是OF COURSE愚蠢的。常量的名称必须传达其含义。除了您的示例是有关数字的基本事实以外,这些基本事实与这些特定数字紧密相关,因此除此以外,没有任何其他意义。
Michael Borgwardt 2013年

17
bool hasApples = apples > 0;

很明显,零表示缺席。我发现0比名为“ absenceValue”的变量更容易理解。


for(int i=0; i < arr.length; i++)

很明显,0是开始位置。我会被一个名为“ firstPosition”的变量所迷惑。这样的变量会让我想知道起始位置是否可以更改。


14

在建议某些内容是否应为常量声明时,我将建议三个关键因素:

  1. 数字是可以准确,简洁地表示的东西吗
  2. 是否有任何可能的情况下需要更改值,但不必重写代码
  3. 看到数字的人比看到命名常数的人更容易或更快地识别它

像pi之类的东西可能应该写为命名常量,而不是数字文字,因为数字文字易于不必要地冗长,不必要地不精确或两者兼而有之。诸如高速缓存中的插槽数量之类的东西可能应该是一个命名常量(尽管请参阅下面的注释),以允许扩展高速缓存而不必修改使用该高速缓存的所有代码的可能性。声明中的数字“ 4”,“ 28”和“ 29”之类的if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;名称可能不应该被命名为常量,因为该表达式几乎肯定比更具可读性if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;。请注意,标准的维护者已指出该年2100年2月的长度与上述公式不符, 妨碍正确处理此类日期的障碍(即代码不会因整数溢出或其他此类问题而跳闸)。

规则2的一个重要警告是,在某些情况下,代码可能以某种难以依靠命名常量表示的方式依赖于硬编码数字。例如,一种计算作为离散参数传递的两个向量的叉积的方法仅在用于三维向量时才有意义。所需的维数不是在不完全重写例程的情况下可以有意义地更改的值。即使预见到可能需要计算三个4维向量的叉积,对于值“ 3”使用命名常量也无济于事,因此更容易满足这一需求。


4

从所有原则上来说,这是一个程度的问题。一般而言,源代码中的数字文字越大,越容易怀疑。最大长度(例如10)或内存地址(例如0x587FB0)显然是不好的做法-几乎可以肯定的是,迟早您将不得不多次重复这些值,从而在不存在这种情况的地方存在不兼容和细微错误的风险改变了。

0在刻度的另一端;它仍然是可疑的,但不是那么多。您是否使用0作为标记值?那么您可能应该改用符号常量,仅因为该常量可以解释其含义。它是否是一种根深蒂固的文化协议,例如“ 0意味着成功完成”?可能没关系。它是否意味着“集合中的第一项”?这可能是无害的,但是如果有其他替代方法(例如,first()我可能更喜欢)。


1
“您是否使用0作为标记值?” <-您能在这里解释“哨兵”的意思吗?我找不到似乎匹配的定义。
rory.ap 2015年

3

从上下文中不能立即看出的每个未命名数字都是一个幻数。定义具有从上下文中立即显而易见的含义的数字有点愚蠢。

在Django(Python网络框架)中,我可以使用一些原始数字定义一些数据库字段,例如:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

比说的更清楚(和推荐的做法

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

因为我不太可能需要更改长度(并且可以始终max_length与字段的长度进行比较)。如果在最初部署应用程序后确实需要更改字段的长度,则需要在Django代码中每个字段的一个位置精确地更改它的长度,然后另外编写一个迁移以更改数据库的架构。如果我需要引用max_length某个对象类型的已定义字段,则可以直接执行-如果这些字段正在定义一个Person类,则可以Person._meta.get_field('firstname').max_length用来获取max_length被使用(在一个地方定义)。相同的40用于多个字段的事实无关紧要,因为我可能想独立更改它们。名的长度绝不能取决于中间名或姓的长度;它们是独立的值,可以独立更改。

通常,数组索引可以使用未命名的数字。就像如果我有一个要放入python字典的数据的CSV文件,而该行中的第一个元素是key我要编写的字典:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

当然,我可以命名index_column = 0并执行以下操作:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

或更糟的定义after_index_col = index_col + 1是摆脱index_col+1,但这在我看来并没有使代码更清晰。另外,如果我给它起index_col一个名字,那么即使该列不为0,也最好使代码正常工作(因此该row[:index_col] +部分)。


7
实际上,max_lngth=40vs max_length=MAX_LENGTH_NAME是一个经典的魔术数字示例,它尖叫着成为一个符号。您想要支持45个字符名称的日子到了,现在怀疑每个使用“ 40”的字符都必须仔细检查。
Ross Patterson

1
@RossPatterson-我们不断将它与全局变量MAX_ARRAY_SIZE进行比较的不是C,而是一个不错的Web框架。唯一出现幻数的地方是您声明数据库模型的位置;将其他所有内容都与此值进行比较(例如,代码40中的其他任何地方都没有出现)。还要注意,如果不将模式变量绑定到数据库,则无法轻松更改此变量。如果我想更改为1个字符的中间名,将其立即更改为40即可1。您必须考虑上下文。
jimbob博士13年

2
抱歉,您在两点上错了。首先,OP提出了一个“编程实践”问题,该问题未指定任何语言。他们说的是“方法”,而不是“功能”,所以让我们假设一些面向对象的方法,但这并不能使我们脱离魔幻编号的数据领域。其次,如果将幻数添加到数据库(例如,架构)中,则将其包含在代码中甚至更糟。正确的做法是从源头上获得神奇的常数-数据库本身或将所有这些常数集中于整个生命周期的模式模块的模式模块。
罗斯·帕特森
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.