公式中的魔数

4

众所周知,应该避免使用神秘数字。但是对于复杂的条件公式中的神秘数字呢?例如:

int result = 0;
if (level <= 50) {
    result = (int) (Math.pow(level, 3) * (100 - level) / 50);
}
else if (level <= 68 && level > 50) {
    result = (int) (Math.pow(level, 3) * (150 - level) / 100);
}
else if (level <= 98 && level > 68) {
    result = (int) (Math.pow(level, 3) * ((1911 - 10 * level) / 3) / 500);
}
else if (level < 100 && level > 98) {
    result = (int) (Math.pow(level, 3) * (160 - level) / 100);
}
return result;

在这种情况下,仅仅说“在可能的情况下避免魔法数字”是否更好?我也在使用Eclipse中的CheckStyle来帮助我找出可能会错过实际魔法数字的地方。然而,没有办法禁用某些数字的检查而不禁用其他数字的检查。


2
这里显示的大多数公式仍然非常相似,为什么不用变量替换值,并在if检查中进行赋值。 - Shailesh Aswal
2
在这个特定的地方,你必须避免使用神奇数字。除了你之外,没有人能够说出为什么需要 1911 - 10 * level 等等。1911是什么?68又是什么?没有人知道。 - Konstantin V. Salikhov
1
投票关闭,因为基于个人观点。但在我看来,如果魔数对业务逻辑至关重要,那么让它存在应该是可以接受的。请参考以下链接,以获取更详细的讨论:http://thedailywtf.com/Articles/Soft_Coding.aspx - shree.pat18
经过思考你的魔数,我同意Konstantin Salikhov的观点 - 它们似乎并不是通用的魔数。它们只是针对你的情况或想法特定目的的常量。 - Stefan
我并不是这些方程的创造者,也不知道这些数字的含义。这是一个在视频游戏中用来计算经验值以达到下一级别的方程式,定义在这里 - Zymus
显示剩余2条评论
7个回答

5

我认为所有的魔数都应该声明为常量或者进行适当的文档说明。

首选声明并进行文档说明。

否则,开发人员无法清楚地知道这些数字是什么以及它们存在的原因。


我同意。将“不寻常的出现”作为一个具有良好解释名称的常量是一个很好的开始。无论如何,它们都应该在代码中记录。 - Stefan

2

不仅需要用命名常量替换魔法数字,我建议使用一些命名的中间变量。例如,((1911 - 10 * level) / 3) / 500)的意义是什么?应该分配一个有意义的变量名。这是社会保障金支付和年龄吗?

OP既不理解数字也不理解方程式,这回答了这个问题。他将如何支持这个代码?


0
你可以尝试类似于以下的方法:(这并不完整,尽量想出单一的公式/方程,并按照检查替换变量中的值)
int d = 1;
    int x = 3;
    int l = 1;
    int f = 1;
    int y = 0;

    if (level <= 50) {
        y = 100;
        d = 50;
    } else if (level <= 68 && level > 50) {
        y = 150;
        d = 100;
    } else if (level <= 98 && level > 68) {
        y = 1911;
        l = 10;
        f = 500;
        d=3;
    } else if (level < 100 && level > 98) {
        y = 160;
        d = 100;
    }
    return (int) (Math.pow(level, x) * ((y - level * l) / d) / f);

0
如Rafael所提到的,魔数应该被声明为常量并进行适当的文档记录。
另外,为了进一步改善代码,您正在执行相同的操作三次(我不确定您在第三个if条件中做了什么,如果这些具有相同的上下文,可以考虑它为四次),因此您可以声明一个方法来提高代码的可读性,如下所示。
public int calcResult(int arg1, int arg2, int arg3)
{
   return (int)(Math.pow(level, arg1) * (arg2 - level) / arg3);
}

.... 

int result = 0;
if (level <= 50) {
    result = calcResult(3,100,50); // these can be replaced with constants.
}
else if (level <= 68 && level > 50) {
    result = calcResult(3, 150, 100);
}
else if (level <= 98 && level > 68) {
    result = (int) (Math.pow(level, 3) * ((1911 - 10 * level) / 3) / 500); // You can also change this accordingly.
}
else if (level < 100 && level > 98) {
    result = calcResult(3, 160, 100);
}
return result;

上面的代码对用户来说更易读,因为同样的操作是由一个方法执行的。用常量替换魔数会使其更加优化。

1
正如我在原帖中所说,这些方程式不是我想出来的,我也不知道这些数字的含义。这是一个用于计算视频游戏中升级所需经验值的方程式,定义在这里 - Zymus
@Zymus 的链接很有帮助,是个好发现。 (y) - Not a bug

0

编程并不是一件有硬性规则的事情。编程是关于做出权衡的一切。为了做出好的权衡,我们有商定的良好实践。对神秘数字表示不满就是其中之一。

这并不意味着神秘数字应该总是被避免。以下示例是一个避免使用魔法数字实践的愚蠢用法:

final int ONE = 1; // as if the value of 1 is going to change in the future

但这是可以接受的:

final int BITS_TO_SHIFT = 1;
// and further down in the code
int shifted_value = value >> BITS_TO_SHIFT;

话虽如此,让我们看看您提供的代码是否真的需要魔数:

  1. 我们可以将魔数移动到常量中:

    final int CUTOFF_1 =  50;
    final int CUTOFF_2 =  68;
    final int CUTOFF_3 =  98;
    final int MAX      = 100;
    
    
    int result = 0;
    if (level <= CUTOFF_1) {
        result = (int) (Math.pow(level, 3) * (100 - level) / 50);
    }
    else if (level <= CUTOFF_2 && level > CUTOFF_1) {
        result = (int) (Math.pow(level, 3) * (150 - level) / 100);
    }
    else if (level <= CUTOFF_3 && level > CUTOFF_2) {
        result = (int) (Math.pow(level, 3) * ((1911 - 10 * level) / 3) / 500);
    }
    else if (level < MAX && level > CUTOFF_3) {
        result = (int) (Math.pow(level, 3) * (160 - level) / 100);
    }
    return result;
    
  2. 我认为这看起来更好。只有在截止数字发生变化时才需要更新常量。我相信大多数其他常量也可以通过这种方式删除。但是,如果不知道它们是什么,我就无能为力。

请注意,作为一个陌生人,我将那些神奇的数字视为一组任意数字。创建具有描述常量目的的适当名称的常量是否更好?

正如我在原帖中所说,这些方程式不是我想出来的,我也不知道这些数字的含义。这是一个用于计算视频游戏中下一级所需经验值的方程式,定义在这里 - Zymus
那么几乎可以肯定,在游戏的未来版本中,升级标准将会改变。我认为你应该为常量找一些好的名称。 - sampathsris
在Javadoc中添加一个链接到网页并定义模糊常量是否可接受?这样用户至少知道我没有随意创建数字。 - Zymus
当然。我认为有更多的文档来解释这些任意值会更好。 - sampathsris

0

我会先尝试简化代码。

int l3 = level * level * level;

return level <= 50 ? l3 * (100 - level) / 50 :
        level <= 68 ? l3 * (150 - level) / 100 :
                level <= 98 ? l3 * ((1911 - 10 * level) / 3) / 500 :
                        level < 100 ? l3 * (160 - level) / 100 : 0;

然后我会看一下如果级别>=100为什么结果为0。

由于这似乎是随意的,解释这些魔数可能会很困难,因为你可能是凭空想出来的,但同样地,你可能会发现可以简化公式,使其更容易计算和解释,同时仍具有所需的形状。


0

这有点是风格和品味的问题。一般的建议是:当上下文明显表明你的意思和原因时,或者当使用魔数本身就是它们的定义时,即没有其他解释说明数字的含义和原因时,使用魔数。例如,如果我定义一个函数:

0 if x < 28 else 1

关于这个数字,没有比“我用来定义函数的门槛”更具体的描述。如果在你的程序中没有其他常数等于28,也永远不会有,那么你可能可以将其保留。

在你的情况下,你可能会通过这个公式来定义这些门槛,并且对它们没有什么可说的,那么你可以保持原样。

如果你能给这个数字起个名字,并/或者解释为什么这个数字有这个值,那么最好使用一个名字:

var age  = 52; // my age
var code = 52; // my country's long distance code

即使您对一个数字没有任何发言,给它命名也总是更好的选择,因为如果您有两个这样的数字,您以后将不知道它们是否独立。 即使现在它们不相等,但某天它们可能会相等。 在上面的例子中,明年我将年满53岁,并且必须修改公式:

a = 52 * x + y + z + 52;

但是这两个是我的年龄和哪一个是代码?一年前我写这个程序的时候它是

a = 51 * x + y + z + 52;

对我来说,我的年龄和我的代码很明显,但在我52岁后修改程序后就不是那么明显了!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接