zoukankan      html  css  js  c++  java
  • 代码细节重构:请对我的代码指手划脚(一)

    “请对我的代码指手划脚”是我们群内搞的一个不定期的常规性活动,以代码审阅和细节重构为主线,大家可以自由发表自己的意见和建议,也算得上是一种思维风暴。感觉到这个活动很有意义,有必要总结并记录下来。今天我发起了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

  • 相关阅读:
    如何选择一家公司?
    教你一招最屌的阅读开源项目的姿势
    我是如何管理我的团队的?
    我面试到底问什么?
    如何正确使用开源项目?
    如何选择开源项目?
    html表格中的tr td th用法
    如何用 Java 实现 Web 应用中的定时任务?
    java定时任务实现的几种方式
    Java 定时任务 & 任务调度
  • 原文地址:https://www.cnblogs.com/ymind/p/2469067.html
Copyright © 2011-2022 走看看