2010-11-08 102 views
2

你会如何重构这段代码?你会如何重构这段代码?

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

看起来很凌乱我,我知道我可以挤在同一行,但我很想知道别人会怎么做。

由于

+0

下面的一些答案令我难过。 :*(他说重构,而不是反射器 – 2010-11-08 22:26:43

+3

真的很奇怪的是,使用双打给你四舍五入的错误 – Ishtar 2010-11-08 22:53:22

回答

7

假设intaintbintc被分类为int/Int32然后Convert.ToDouble是基本相同的简单铸造到double

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

这实际上是否值得重构是另一回事。即使您不需要这些中间结果,将中间计算作为单独的语句来提高可读性通常也更有意义。当然,具有有意义的变量名称使的区别。

+0

+1 - 打我的答案,以及添加一些务实的建议。 – ChaosPandion 2010-11-08 22:38:42

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

(固定)

+1

不会。进行整数除法 – 2010-11-08 22:27:18

+0

哎呀,我的(双重)演员在括号的错误一侧 – 2010-11-08 22:39:44

+0

只需将其删除;使用您的编辑,它与接受的答案相同 – 2010-11-09 00:35:27

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? 

但实际上我宁愿只是不要管它!

+0

我会给变量一个更有意义的名称,除此之外,这对我来说也是+1 – Zac 2010-11-08 22:49:03

+0

@Zac:我也想要更多有意义的名字,但在这种情况下,不可能知道它们的真实含义。 – FrustratedWithFormsDesigner 2010-11-09 04:27:34

1

首先我会给p,v,inta,intb等有意义的名字。

前两行可以组合:

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

+1对于有意义的名称 – egrunin 2010-11-08 22:41:23

+0

我将它们重命名为张贴在这里!谢谢 – 2010-11-08 22:47:39

0

当然这只是

inta * intc/intb 

如果您不需要p的价值明确,你不需要做任何造型在所有。

+0

疯狂!“nope。这将执行整数除法”引用来回某人.. – Ishtar 2010-11-08 22:30:20

+1

不是真的,该部门必须双倍,否则你可能会失去精度 – 2010-11-08 22:30:35

+2

+1为什么他被拒绝,他是对的.... – Doggett 2010-11-08 22:40:14

0

上@ FrustratedWithFormsDes的答案的变化:

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

return (int) (doubleA/doubleB); 
0

有几个有趣的点,没有其他人似乎已经覆盖了,所以我会加进来......

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

所以(假设非巨大的国际价值,并接受我们可能会抛出一个div被零除外)的最终结果(而不是重命名为清楚起见)可能是:

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

这不是与接受的答案有很大的不同,但在某些平台上可能会稍微好一些(通过使用整数乘法而不是双重算法)。

4

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

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

一个使用转换和模具之间的区别是,转换将抛出一个异常的出界,但铸造不会和铸造为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会更频繁地出现偏差。

始终验证您的重构是否针对自动化测试。不要以为计算机的价值观就像高中数学中的数字一样。

+0

+1完美。没有测试就不重构。 – Ishtar 2010-11-09 13:28:31

+0

哦,顺便说一下,所有失败发生在'numer * initialValue%denom == 0'? (忽略溢出)。 – Ishtar 2010-11-09 13:36:50