你会如何重构这段代码?

时间:2010-11-08 22:20:27

标签: c#

你会如何重构这段代码?

double p = (Convert.ToDouble(inta) / Convert.ToDouble(intb)) * 100;
double v = (p / 100) * Convert.ToDouble(intc);
return (int)v;

对我而言似乎非常混乱,我知道我可以把它挤到一条线上,但我有兴趣知道其他人会怎么做。

由于

9 个答案:

答案 0 :(得分:7)

假设intaintbintc被输入为int / Int32,那么Convert.ToDouble基本上与简单投射相同到double

return (int)((inta / (double)intb) * intc);

这实际上是否值得重构是另一回事。将中间计算保持为单独的语句以提高可读性通常更有意义,即使您不需要这些中间结果。当然,拥有有意义的变量名会产生的差异。

答案 1 :(得分:4)

说真的,不要。代码有什么问题 - 忽略可能存在的数学问题,只是查看代码结构本身?

我不会重构。将它全部压缩到一行将使其难以阅读。如果我绝对有做某事,我会为a,b和c的双重版本创建新变量,如下所示:

//set up variables
double doubleA = Convert.ToDouble(inta);
double doubleB = Convert.ToDouble(intb);
double doubleC = Convert.ToDouble(intc);


//do calculations
double p = (doubleA / doubleB) * 100
double v = (p / 100) * doubleC; //why did we divide by 100 when we multiplied by it on the line above?
return (int)v; //why are we casting back to int after all the fuss and bother with doubles?

但实际上我宁愿不管它!

答案 2 :(得分:4)

好吧,首先我会使用更有意义的名字,并且猜测这是采用整数比率,将其转换为百分比,将该百分比应用于另一个原始值,并返回一个新值,即结果被截断为整数。

double percent = (Convert.ToDouble( numer ) / Convert.ToDouble( denom )) * 100;
double value = (percent / 100) * Convert.ToDouble( originalValue );
return (int)value;

使用Convert和强制转换之间的一个区别是Convert将为越界引发异常,但是不会强制转换,并且转换为int会导致Int32.MinValue。因此,如果value对于int或InfinityNaN而言太大或太小,您将获得Int32.MinValue而不是最后的异常。其他转换器不能失败,因为任何int都可以表示为double。

所以你可以使用没有改变含义的强制转换来编写它,并利用这样一个事实:在一个涉及整数和双精度的表达式中,整数被自动转换成双精度:

double percent = ((double) numer ) /  denom ) * 100;
double value = (percent / 100) * originalValue;
return (int)value;

现在,C#在分配到15-16时截断了双重结果,但它的实现定义了中间体是否以更高的精度运行。我认为这不会改变可以转换为int的范围内的输出,但我不知道,并且值空间太大而无法进行详尽的测试。因此,如果没有完全的规范,那么函数的目的是什么,你可以更改其他内容并确保不会更改输出。

如果比较这些重构,每个重构都是天真的数学等价物,并通过它们运行一系列值:

    static int test0(int numer, int denom, int initialValue)
    {
        double percent = (Convert.ToDouble(numer) / Convert.ToDouble(denom)) * 100;
        double value = (percent / 100) * Convert.ToDouble(initialValue);
        return (int)value;
    }

    static int test1(int numer, int denom, int initialValue)
    {
        return (int)((((((double)numer) / denom) * 100 ) / 100 ) * initialValue);
    }

    static int test2(int numer, int denom, int initialValue)
    {
        return (int)((((double)numer) / denom) * initialValue);
    }

    static int test3(int numer, int denom, int initialValue)
    {
        return (int)((((double)numer) * initialValue) / denom);
    }

    static int test4(int numer, int denom, int initialValue)
    {
        if (denom == 0) return int.MinValue;
        return (numer * initialValue / denom);
    }

然后,您会得到以下结果,即计算testN不等于test0并让它运行几个小时的次数:

numer in [-10000,10000] 
denom in [-10000,0) (0,10000] 
initialValue in [-10000,-8709] # will get to +10000 eventually

test1 fails = 0 of 515428330128 tests, 100% accuracy.
test2 fails = 110365664 of 515428330128 tests, 99.9785875828803% accuracy.
test3 fails = 150082166 of 515428330128 tests, 99.9708820495057% accuracy.
test4 fails = 150082166 of 515428330128 tests, 99.9708820495057% accuracy.

因此,如果您想要一个完全等效的函数,那么您似乎可以到达test1。虽然100s应该在test2中取消,但实际上它们会在一些边缘情况下影响结果 - 中间值的舍入推动值的一侧或另一个整数。对于此测试,输入值在-10000到+10000范围内,因此test4中的整数乘法不会溢出,因此test3test4是相同的。对于更宽的输入范围,test4将更频繁地偏离。

始终验证您对自动化测试的重构。并且不要认为计算机所运用的值与高中数学中的数字相似。

答案 3 :(得分:1)

首先,我将给出p,v,inta,intb等有意义的名称。

前两行可以合并:

double pv = ((double)inta/intb)*intc;
return (int)pv;

答案 4 :(得分:0)

return (int)(Convert.ToDouble(inta * intc) / Convert.ToDouble(intb));

答案 5 :(得分:0)

return (int)(((double)inta / intb) * intc);

<强>(固定)

答案 6 :(得分:0)

当然这只是

inta * intc / intb

如果您不需要显式的p值,则根本不需要进行任何转换。

答案 7 :(得分:0)

@ FrustratedWithFormsDes答案的变体:

double doubleA = (double) (inta * intc);
double doubleB = (double) intb;

return (int) (doubleA / doubleB);

答案 8 :(得分:0)

有一些有趣的观点似乎没有其他人覆盖,所以我会加入混合......

  • 我要做的第一次重构就是使用好的命名。三行代码很好,但“p”,“v”和“inta”是可怕的名字。
  • 如果“inta”等不可转换为double,则Convert.ToXXX可能会抛出异常。在这种情况下,你可以使用double.TryParse()或try ... catch来使这个代码对任何类型都健壮。 (当然,正如许多人所提到的,如果值只是整数,那么(双)强制转换就足够了。)
  • 如果intb的值为0,那么您将得到除以零的异常。因此,您可能希望在使用之前检查intb是否为非零。
  • 所以对于数学...... * 100和/ 100将取消,所以毫无意义。假设输入是整数(而不是巨大的),那么如果你在进行除法之前将int乘以intc,则可以消除一个(双重)操作,因为(int * intc)可以以整数精度安全地完成。

所以(假设非巨大的int值,并接受我们可能抛出一个div-by-zero异常)最终结果(为了清晰起见没有重命名)可能是:

return((int) ((inta * intc) / (double) intb));

它与接受的答案没有太大的不同,但在某些平台上表现稍好一些(通过使用整数乘法而不是双精度乘法)。