好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

代码细节重构:请对我的代码指手划脚

代码细节重构:请对我的代码指手划脚

“请对我的代码指手划脚”是我们群内搞的一个不定期的常规性活动,以代码审阅和细节重构为主线,大家可以自由发表自己的意见和建议,也算得上是一种思维风暴。感觉到这个活动很有意义,有必要总结并记录下来。今天我发起了4短代码,都有一定的代表性。今天我就其中的一个代码片段的重构做一个简单的总结和分享。

首先我们看看目标代码:

  1   public   static   string  TestA(List< string >  items)
   2   {
   3       var  builder =  new  StringBuilder( );
   4  
  5       foreach  ( var  item  in   items)
   6       {
   7           if  (builder.Length >  0  )
   8           {
   9              builder.Append( "  |  "  );
  10               builder.Append(item);
  11           }
  12           else   builder.Append(item);
  13       }
  14  
 15       return   builder.ToString();
  16  }

这里我使用C#来做示例,实际上,语言是相通的,我们将要谈论的优化技巧在大多数编程环境中都是通用的。针对如上代码,总计收集到了如下优化建议。

建议一:代码重用性

我们可以看到,if...else...子句中有一段“builder.Append(item);”代码是重复的,改变流程可以让它们只出现一次,重构后结果如下:

 1   foreach  ( var  item  in   items)
  2   {
  3       if  (builder.Length >  0 ) builder.Append( "  |  " );  //   去掉了大括号 
 4  
 5       builder.Append(item);
  6  }

建议二:性能优化

我们知道StringBuilder类的构造函数中有一个capacity参数,这个参数意味着StringBuilder对象初始化时预分配的内存大小。如果能够适当的设定一个值,那么对提升性能应该会很有帮助。因为这可以减少内存分配的次数,StringBuilder默认情况下是以2的N次方的形式不断翻倍来调整内存需求的(对于我们来说,这个过程是自动的)。

建议三:还是性能优化

建议将foreach拆分为一次手工Append和一个for循环,如此可以避免在foreach内部的if判断。在数据量大的时候可以大大的减少CPU的时钟周期的占用,这个建议很不错!重构后代码如下:

  1   public   static   string  TestA(List< string >  items)
   2   {
   3       //   这里是个capacity优化的假设值,实际运行中需要不断的测试调优 
  4       var  builder =  new  StringBuilder( 100000  );
   5  
  6      builder.Append(items[ 0  ]);
   7  
  8       for  ( var  i =  1 ; i < items.Count; i++ )
   9       {
  10           var  item =  items[i];
  11  
 12          builder.Append( "  |  "  );
  13           builder.Append(item);
  14       }
  15  
 16       return   builder.ToString();
  17  }

建议四:内存优化

其实是综合了建议二和建议三,如下:

  1   public   static   string  TestA(List< string >  items)
   2   {
   3       var  length = items.Sum(t =>  t.Length);
   4  
  5      length += (items.Count -  1  );
   6  
  7       if  (length ==  0 )  return   String.Empty;
   8  
  9       //   先计算出capacity的值 
 10       var  builder =  new   StringBuilder(length);
  11  
 12      builder.Append(items[ 0  ]);
  13  
 14       for  ( var  i =  1 ; i < items.Count; i++ )
  15       {
  16          builder.Append( "  |  "  );
  17          builder.Append(items[i]);  //   消灭了之前的一个局部变量,减少内存分配 
 18       }
  19  
 20       return   builder.ToString();
  21  }

我的答复

其实,我出这个题目并不是为了考察性能优化、内存优化等问题,不过猴子们能想出各种招数来解题,我真的很欣慰!至少大家都在参与,都在动脑筋!这是好事!

通读这篇文章之后,我相信您已经发现了题目原本的业务逻辑是想把一个string集合中的字符串使用“|”字符串联起来,而且不能在结果字符串的两边出现“|”。因此,我期望能有童鞋想出如下的重构建议:

 1   public   static   string  TestB(List< string >  items) 
  2   { 
  3       return  String.Join( "  |  "  , items);
  4  }

您会不会感觉到我这么说很坑爹呢?

是的!代码的细节重构不仅仅是各种优化和代码的写法、编码体验、编码规范等,还有个重要的地方就是业务逻辑!编程是什么?编程是处理数据的手段和过程,同样的结果可能会有很多途径抵达,对我们来说,要从这些途径中挑选出最简单易用的,性能差异不要太大就可以了。

性能测试往往具有很强的随机性,所以我们的测试必须要在不同的数量级下反复测试多遍,然后收集一个平均结果(最好是去掉最大值和最小值)来对比。至于性能差异的大小,在同一个数量级之内的,我们都认为“差异不大”或“没有差异”,超出两个数量级的时候一定要警惕!

本文附带了我编写的测试代码,大家可以下载后运行对比一下。我随机选择了一个测试结果供大家参考:

 测试数据准备完成,请按任意键继续……
     itemCount  StringBuilder    String.Join
             1       1.851500       0.318600
            10       0.027500       0.064400
           100       0.225500       0.261600
          1000      10.104700       2.324100
         10000      19.039900      20.094800
        100000     216.185100     251.624600
       1000000    2364.580300    3401.948900
      10000000   22862.921600   33593.679800
测试完毕! 

可以看出,我们的建议四和String.Join方法的性能差异其实很小,可以忽略不计,通常我们谁会去处理一个几百万上千万这么大的集合呢?

至于这两种方法为什么差异不大,其实,我们只需要看看String.Join方法的实现就知道了,通过 .NET Reflector反编译后我们发现它的实现也使用了类似于建议四的方案:

  1   [SecuritySafeCritical]
   2   public   static   unsafe   string  Join( string  separator,  string [] value,  int  startIndex,  int   count)
   3   {
   4       if  (value ==  null )  throw   new  ArgumentNullException( "  value  "  );
   5       if  (startIndex <  0 )  throw   new  ArgumentOutOfRangeException( "  startIndex  " , Environment.GetResourceString( "  ArgumentOutOfRange_StartIndex  "  ));
   6       if  (count <  0 )  throw   new  ArgumentOutOfRangeException( "  count  " , Environment.GetResourceString( "  ArgumentOutOfRange_NegativeCount  "  ));
   7       if  (startIndex > (value.Length - count))  throw   new  ArgumentOutOfRangeException( "  startIndex  " , Environment.GetResourceString( "  ArgumentOutOfRange_IndexCountBuffer  "  ));
   8       if  (separator ==  null ) separator =  Empty;
   9       if  (count ==  0 )  return   Empty;
  10              
 11       var  length =  0  ;
  12       var  num2 = (startIndex + count) -  1  ;
  13              
 14       for  ( var  i = startIndex; i <= num2; i++)  if  (value[i] !=  null ) length +=  value[i].Length;
  15              
 16      length += (count -  1 )* separator.Length;
  17              
 18       if  ((length <  0 ) || ((length +  1 ) <  0 ))  throw   new   OutOfMemoryException();
  19       if  (length ==  0 )  return   Empty;
  20              
 21       string  str =  FastAllocateString(length);
  22              
 23       fixed  ( char * chRef = & str.m_firstChar)
  24       {
  25           var  buffer =  new   UnSafeCharBuffer(chRef, length);
  26           buffer.AppendString(value[startIndex]);
  27                  
 28           for  ( var  j = startIndex +  1 ; j <= num2; j++ )
  29           {
  30               buffer.AppendString(separator);
  31               buffer.AppendString(value[j]);
  32           }
  33       }
  34  
 35       return   str;
  36  }

代码就不再解读了,大家可以慢慢体会。

本文想告诉大家的是:代码细节重构不要只停留在语言表面上,深入业务逻辑有时候会得到意想不到的结果!

代码下载:

代码细节重构-String.Join.zip

【IT技术综合群,纯粹.NET技术讨论请入本群】
网鸟-刺客巅峰:47700865 【NoSQL群】NoSQL系列技术QQ群:
一群:23152359(满员)
二群:193713524(接近满员)
三群:79377097(强烈推荐)
四群:191845335(新建)
入群写明理由,否则一律拉黑! 关于老陈:一只不小心踏入互联网十二年的老猴子,喜欢在群里跟大家伙儿研究和分享各种V。天造的懒汉,十足的吃货!

分类:  C# ,  架构设计

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于代码细节重构:请对我的代码指手划脚的详细内容...

  阅读:46次