因此,我们将作为参数发送给方法的代码中的每个数字都视为幻数?对我来说,不应该。我想,如果让某个数字代表用户名的最小长度,并且我们开始在代码中使用“ 6” ...那么,是的,我们遇到了维护问题,这里的“ 6”是一个魔术数字....但是如果我们正在调用一个方法,该方法的参数之一例如接受整数作为集合的第ith个成员,然后我们将“ 0”传递给该方法调用,那么在这种情况下,我认为“ 0”不是魔术数。你怎么看?
因此,我们将作为参数发送给方法的代码中的每个数字都视为幻数?对我来说,不应该。我想,如果让某个数字代表用户名的最小长度,并且我们开始在代码中使用“ 6” ...那么,是的,我们遇到了维护问题,这里的“ 6”是一个魔术数字....但是如果我们正在调用一个方法,该方法的参数之一例如接受整数作为集合的第ith个成员,然后我们将“ 0”传递给该方法调用,那么在这种情况下,我认为“ 0”不是魔术数。你怎么看?
Answers:
如果数字的含义在上下文中非常清楚,那么我认为这不是“魔术数字”问题。
示例:假设您尝试获取字符串的子字符串,从开始到某些令牌,并且代码看起来像这样(虚构语言和库):
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;
360
,在理解大多数人会知道这意味着什么的情况下,使用一个数字来标记一个完整的旋转是公平的。是提供常数就不会受到伤害的情况)
0
在我的子字符串示例的情况下代替。在这种情况下,这可能是它们可能造成的最少损失。自从我进行几何计算的编码以来已经有很长时间了,但是通常,值15、30、45、60、90、180、360是可以接受的常数。我从未见过有人定义FIFTEEN_DEGREES
...
在建议某些内容是否应为常量声明时,我将建议三个关键因素:
像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”使用命名常量也无济于事,因此更容易满足这一需求。
从所有原则上来说,这是一个程度的问题。一般而言,源代码中的数字文字越大,越容易怀疑。最大长度(例如10)或内存地址(例如0x587FB0)显然是不好的做法-几乎可以肯定的是,迟早您将不得不多次重复这些值,从而在不存在这种情况的地方存在不兼容和细微错误的风险改变了。
0在刻度的另一端;它仍然是可疑的,但不是那么多。您是否使用0作为标记值?那么您可能应该改用符号常量,仅因为该常量可以解释其含义。它是否是一种根深蒂固的文化协议,例如“ 0意味着成功完成”?可能没关系。它是否意味着“集合中的第一项”?这可能是无害的,但是如果有其他替代方法(例如,first()
我可能更喜欢)。
从上下文中不能立即看出的每个未命名数字都是一个幻数。定义具有从上下文中立即显而易见的含义的数字有点愚蠢。
在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] +
部分)。
max_lngth=40
vs max_length=MAX_LENGTH_NAME
是一个经典的魔术数字示例,它尖叫着成为一个符号。您想要支持45个字符名称的日子到了,现在怀疑每个使用“ 40”的字符都必须仔细检查。
40
即可1
。您必须考虑上下文。