这是象征性的不断过度使用吗?


34

我刚接触软件工程,因此作为一项学习练习,我写了一个国际象棋游戏。我的朋友看了一下,并指出我的代码看起来像

for (int i = 0; i < 8; i++){
    for (int j = 0; j < 8; j++){

他坚持认为应该改为

for (int i = 0; i < CHESS_CONST; i++){
    for (int j = 0; j < CHESS_CONST; j++){

带有一些更好的符号名称,我现在就不用考虑了。

现在,我当然知道通常会避免使用幻数,但是我觉得

  1. 这个数字永远不会改变;
  2. 该名称无论如何都不能这么具有描述性,因为在整个代码中很多地方都使用了该数字;和
  3. 任何经过国际象棋程序源代码的人都应该对国际象棋有足够的了解,以便知道8是什么,

确实不需要符号常量。

那么你们怎么看?这是矫kill过正吗,还是我应该遵循惯例并使用符号?


46
CHESS_CONST比仅使用数字8更糟糕,但是使用描述性名称的常量会有所改善。您说任何人都应该知道8在代码中的含义,但这不是事实。一个没有上下文的整数文字可能意味着许多事情,例如许多动作,板上的棋子等等。常量的描述性名称可以使意图更清楚,从而使代码更易于理解。
JacquesB

43
就个人而言,我发现比魔术数字更令人讨厌的是名称ij循环变量。我一生无法弄清楚哪个应该代表等级,哪个应该代表档案。等级范围从1..8和文件的范围从a..h,但在你的情况下,无论是ij范围0..7,这样就不会帮我看看哪个是哪个。是否存在一些我不知道的国际性信件短缺危机,或者将它们重命名为rankand 是什么问题file
约尔格W¯¯米塔格

4
扮演恶魔的拥护者。想象一下在某人使用了8号魔幻数字的情况下阅读他们的国际象棋代码,您能假设您知道它的用途吗?您怎么能100%确定?有没有可能意味着别的东西?如果您甚至不必做假设,它会变得更好吗?您可能花费多少时间来遍历代码以确定您的假设是否正确?如果代码使用有意义的,有洞察力的名称进行更多的自我文档记录,您会花费更少的时间吗?
本·科特雷尔

16
@JacquesB:的确如此。有8个等级,8个文件,8个棋子。一些象棋引擎使用点数系统,其中某些棋子,某些区域和某些动作附有点,引擎选择得分最高的动作。这样的成绩可能是8分。8可能是默认的搜索深度。可能几乎是任何东西。
约尔格W¯¯米塔格

9
我会考虑使用一个foreach循环,例如foreach(var rank in Ranks)。我还考虑将两个循环合并为一个,其中每个元素都是一个(行,文件)元组。
CodesInChaos

Answers:


101

恕我直言,您的朋友使用符号名称是正确的,尽管我认为该名称绝对应该更具描述性(例如BOARD_WIDTH而不是CHESS_CONST)。

即使该数字在程序的整个生命周期内都不会改变,在您的程序中可能还会有其他地方出现数字8的含义。BOARD_WIDTH在需要的木板宽度处替换“ 8” ,并在不同的含义下使用另一个符号名称,可以使这些不同的含义清晰,明显,并使整个程序更具可读性和可维护性。如果您需要快速识别代码中取决于电路板宽度的所有位置,它还使您可以对程序进行全局搜索(或者,如果环境允许,可以进行反向符号搜索)。

另请参见该前SE.SE帖子,以获取有关如何(或不愿)选择数字名称的讨论。

附带说明一下,由于已在注释中进行了讨论:如果在实际程序的代码中,如果变量i引用j的是董事会的行和列,或者反之亦然,那么建议选择变量名,明确区分,如rowcol。这样的名称的好处是,它们会使错误的代码看起来不正确。


22
我想补充一下,如果OP编写了一个非常出色的国际象棋引擎,然后希望将其扩展为包括3D国际象棋或其他变体,那么需要更改8s将会是一个挑战。
史蒂夫·巴恩斯

32
@SteveBarnes:我尝试避免使用此类参数,因为它很容易导致诸如“我认为使该程序成为另一种游戏完全不同,因此我可以将魔术数字8保留在原处,这与以往完全不同。 ”
布朗

4
@IllusiveBrian这是一个经典的“稻草人”参数,有两个原因:(1)仅定义一个常量并不意味着程序员仍然不能键入“ 8”(无论是偶然,设计还是恶意!)和(2)仅仅因为常量BOARD_WIDTH = 8并不能使BOARD_WIDTH成为在程序中特定位置使用的正确常量。
alephzero

3
@Blrfl ...或将导致代码过度设计。开发人员有责任意识到将来可能发生的变化并进行相应的编码。像需求这样的编码不会改变而导致问题,但是另一个极端也没有更好。
马尔科姆

4
@corsiKa当循环变量与物理轴相对应时,应将其命名为x,y或row,col,或对于国际象棋,文件,等级。
user949300

11

好的,这里有一些评论:

摆脱魔术数字是一个好主意。有一个称为DRY的概念,该概念经常被错误地表达,但是这种想法是您不会在项目中重复这些概念的知识。因此,如果您有一个名为ChessBoard的类,则可以附加一个名为BOARD_SIZE或ChessBoard.SIZE的常量。这样,此信息就有一个唯一的来源。另外,这有助于以后的可读性:

for (int i = 0; i < ChessBoard.SIZE; i++){
  for (int j = 0; j < ChessBoard.SIZE; j++){

即使数字从未改变,您的程序也可以说是更好的。任何阅读它的人都知道有关代码在做什么的更多信息。

坏名字总比没有名字坏,但这并不意味着不应命名。只需更改名称即可。不要用洗澡水把婴儿扔出去。:p该名称可以是描述性的,只要您充分理解其描述即可。然后,该概念可以用于多种不同的事物。


8
这似乎只是重复已经提出并在最佳答案中解释的观点
gna

-7

您真正想要的是消除对常量的无用引用,无论它们是命名的还是裸露的:

for_each_chess_square (row, col) {
  /*...*/
}

如果您实际上是想通过重复这样的循环来增加常数,那么最好还是坚持一下8

8自我描述;它不是代表其他东西的宏。

您永远不会(TM)将其变成9x9的象棋程序,如果您这样做,则8的扩散将不是主要困难。

我们可以在150,000个行代码库中搜索令牌8,并分类哪些事件以秒为单位。

更重要的是对代码进行模块化,以便将象棋知识集中在尽可能少的地方。最好有一个,两个或三个国际象棋专用模块,其中出现字面数字8,而不是三十七个带有国际象棋专用职责的模块,通过符号名称引用8。

当这8个常数成为程序中压力的来源时,您可以轻松地在那时修复它。修复现在正在发生的实际问题。如果您不觉得自己受到特定8的阻碍,那就顺其自然吧。

假设将来您希望支持替代的电路板尺寸。在那种情况下,这些循环将不得不更改是否使用命名常量或8,因为尺寸将通过诸如board.width和的表达式来检索board.height。如果您使用BOARD_SIZE而不是8,这些地方将更容易找到。这样就省了些力气。但是,你千万不要忘记更换的精力8BOARD_SIZE在了首位。总体努力并不低。在代码上进行一次传递以更改8BOARD_SIZE,然后进行另一遍以支持替代尺寸,这并不比仅仅支持替代尺寸便宜8

我们还可以从纯粹的客观风险/收益分析中看待这一问题。该程序现在具有裸常量。如果将这些替换为常量,则没有任何好处。该程序是相同的。进行任何更改都会带来非零风险。在这种情况下,它很小。尽管如此,没有风险也不应冒险。要面对这种推理“出售”变更,我们必须假设一种好处:将来的好处将对另一个程序有所帮​​助:该程序的未来版本目前不存在。如果正在计划这样的程序,则该假设及其相关推理是善意的,应认真对待。

例如,如果您距离添加更多代码将进一步增加这些常量的距离几天之内,则可能要取消使用它们。如果常量的那些实例大约是将要存在的所有实例,那么为什么要打扰呢。

如果您曾经使用过商业软件,则ROI参数也将适用。如果某个程序没有销售,并且将一些硬编码的数字更改为常量不会提高销售量,那么您将不会为此付出任何努力。零钱投资的时间回报为零。投资回报率的论点超越了金钱。您编写了一个程序,投入了时间和精力,并从中得到了一些收益:这就是您的回报,即您的“ R”。如果仅通过进行更改就可以获得更多的“ R”,无论它是什么,都可以。如果您有一些进一步开发的计划,而这种改变可以改善您的“ R”,则同上。如果更改对您没有立即或可预见的“ R”,请忽略它。


13
8并不是自我描述,而是一个数字,可能意味着任何事情。也许他们确实想做9x9的国际象棋游戏,为什么不呢?如果您有150,000行代码,那么您将无法记住每个8代码中的含义,即使可以,为什么?那只是很多额外的麻烦。至于模块化,用8类似的东西代替NUM_RANKS不会损害模块化,实际上,这很有帮助,因为它使代码更容易修改。
Jerfov2

4
@Kaz我也这样做。然后,我介绍了一些重要的怪异错误,因为很少有用法不是我想的那样,因为很难对大量的替换给予如此多的关注,并且每次都正确。出于这个原因,我首先避免进入这种情况,所以我不能支持纵容这种情况的建议。
doppelgreener

1
“不过,你千万不要忘记更换的精力8BOARD_SIZE在首位” -你会花了审议代码试图弄清楚了每个时间8在你的程序确实几个月从现在起将最有可能超过所花费的时间替换8用一个有意义的模块级常量。
Christian Dean

1
@doppelganger这种情况已经在这里。我不是说,使用一个使用常量的国际象棋程序,并将其替换为8。我也不是说,“计划不要在尚未编写的国际象棋程序中使用常量”
Kaz

1
@Kaz为什么您想让自己不得不阅读8号周围的所有代码,而当您可能只花额外的3秒钟时间输入有意义的名称时,有时可能会弄错上下文?至于知道常数的实际值,查找变量的确切值要比尝试反向工程代码中的某些魔术整数要容易得多
Jerfov2'4
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.