当前位置: 代码迷 >> C# >> 【复建学习】02 何处重构
  详细解决方案

【复建学习】02 何处重构

热度:198   发布时间:2016-04-28 08:26:40.0
【重构学习】02 何处重构?

1、重复代码

  解决方案:

  • 重复代码位于同一个类:提炼成新函数进行调用
  • 重复代码位于不同的子类:提炼成函数放进父类
  • 重复代码位于完全不相干的类:提炼出一个新的类,将重复代码放进新的类中
  • 重复代码并非完全相同,存在些微差异性:用模版方法的设计模式解决

2、函数过长和参数列过长

  修改点:

  • 有注释的地方就可能有修改点,因为一个地方既然值得你去写注释,那么就已经存在将其提炼出来的可能性,即使它只有一行代码
  • 条件表达式和循环处:将分支逻辑和实现细节分离,都可以函数化。而循环可以放到单独的一个函数里。 

  解决方案:

  • 不存在太多参数和临时变量的情况:提炼成新函数进行调用(有太多临时变量和参数,会导致提炼出来的函数可读性可能并不会得到提升)
  • 临时变量多:将临时变量替换成查询方法,再进行提炼
  • 参数过多:参考参数列过长的处理办法
  • 依然存在很多临时变量和参数:用方法对象替代方法,简单地说搞一个类,临时变量作为这个的字段,然后代码提炼为里面的方法

3、过大的类

  解决方案:提炼新的独立类或者子类

4、参数列过长

  解决方案:

  • 对象替代法:将多个参数放入一个参数对象中,再进行传递(不仅提高可读性,因为传递的只是引用所以会少开辟栈空间,提高性能)
  • 函数替代法:看能否将这些参数用函数替代,简单的说就是将这些参数的计算部分放到一个函数里,返回结果再传值

5、发散式变化(一个类受多种变化影响,貌似就是单一职责原则)目标:外界变化和需要修改的类一一对应

  修改点:软件一旦发生多种变化,但是都修改同一个类,说明此类职责重复

  解决方案:找出某种特殊原因造成的所有变化,然后将它们提取到另一个类里面

6、霰弹式修改(一个变化修改多个类)目标:外界变化和需要修改的类一一对应

  修改点:软件一旦发生变化,造成要修改很多类

  解决方案:找出某种变化引起的不同类中的众多修改点,将各个类中修改点放到一个类中

7、依恋情节

  修改点:

  当一个类的函数为了计算经常调用另一个类的一大堆的函数时就表示出现了依恋情节。

  1和0的世界里面出现了如此具有文艺气质的词,那么我们就用文艺的手法去理解:你们家的女朋友经常去找别人家的汉子时,你多多少少需要知道坏了。

  解决方案:

  如果这段代码完全依恋另一个类,那么将此部分出现依恋情节的代码提炼成函数放到另一个类里面(这告诉我们当一个女人完全不爱你的时候要学会放手?)

  然而很多时候并非这么简单,它两个类都有依恋呢?(然而很多时候并非这么简单,她两个都爱呢?)

  那就判断哪个类拥有最多被此函数使用的数据,然后就把此函数和那些数据摆在一起。(判断她爱哪个的优点多一点,就让她去找谁吧?)

  难怪大家都说程序员都是好男人@_@

  然而Martin大神多说了一句:

  如果先提取方法将这段代码分割成不同的数个较小的独立函数上述步骤会简单很多。(请恕我直言,左腿和左手我要了,刀交给你了,你随意?)

8、数据泥团

  修改点:

  所谓数据泥团就是指那些经常一起出现的数据,比如一些经常一起出现的参数,举个例子:分页参数

  解决方案:

  数据泥团应该拥有他们自己的类,就像几个好基友需要让他们住在一起

9、基本类型偏执

  解决方案:

  使用一些小对象比如邮编类,电话号码类将此类数据应用起来

  使用枚举类型将或者小对象去替代那些类型码

10、少用Switch

  解决方案:

  • 用多态来替代Switch,比如用子类去替换状态码,或者用状态模式和策略模式
  • 或者用明确的函数去取代各分支,然后观察是否能将分支去除

11、平行继承体系

  修改点:每当你为某个类增加一个子类时,必须为另外一个类增加子类,那么就有问题

  解决方案:让一个继承体系的实例去引用另一个继承体系

12、冗赘类

  解决方案:如果某个类失去了价值,应该将其去掉,比如父子类之间区别不大,那么可以合并,或者将其搬移到另一个类里

13、夸夸其谈未来性

  不要去考虑未来性,如果用不到就不值得,未来的事情就交给未来,做好现在的事情即可

14、令人迷惑的暂时字段

  修改点:某个实例变量仅为某种特定情况而设,这样的代码就会不易理解,因为你会认为所有时候都会需要它的所有变量。

  解决方案:

  • 通过传递NUll参数在变量不合法的情况下传递NULL
  • 提炼新的对象以适应此种特殊情况

15、过度耦合的消息链

  修改点:一个对象请求另一个对象,然后后者再请求一个变量,然后后者再请求一个变量这就是消息链

  解决方案:

  隐藏委托,在前一个类中写一个函数直接调用第三个类的函数,即尝试把函数链写在第一个类的一个新函数中 

16、中间人

  修改点:

  过度运用委托关系

  解决方案:

  • 移除中间人,直接和对象打交道
  • 当函数主体和函数名的可读性一样清晰时,将其放进调用端
  • 用继承替代委托

17、狎昵关系

  当看到这两个字的时候我查了一下字典,狎妓的狎(xia),一个比较猥琐的字眼,当然你不介意的话也可以称为搞基关系.

  修改点:

  搞基关系的意思是,两个类之间花太多时间探究彼此的private部分,就像两个男人喜欢探究对方的私处一样。

  解决方案:

  • 通过移动字段和移动方法让两个好基友分开(好残忍,割鸡解决的方式太可怕了
  • 将双向关联改为单向关联(也就是说将其中一个变性为妹子就好了
  • 通过提取新类的方式将两者之间的复杂关系放在一个新类里(将他们分开,让他们都通过另一个人对话
  • 如果是父子类关系,那么应该用委托替代继承,让子类不继承父类,而把父类对象作为子类的一个属性(......⊙﹏⊙‖∣) 

18、异曲同工的类

  解决方案:

  其实差不多就是重复代码的问题,就是两个做相同事情的函数或者类,唯一的区别在于,当这两个类差不多的情况下可以提取一个父类

  来解决问题

19、不完美的库类

  修改点:

  简而言之就是别人写的类不好用时,就比如一个引用的动态链接库,你又改不了这个东西时

  解决方案:

  • 添加较少函数或者应用不普遍的情况:就是在客户类中添加一个函数,然后传个服务类对象进去
  • 添加较多函数或者应用普遍的情况:就是自己再写一个类去继承这个不能改写的类

  以下并不是重构作者的解决方案:

  因为这本书真的是太老了

  对于应用.net的我们可以考虑用一下partial类或者扩展函数

  partial类示例:

namespace 重构测试程序{    class Program    {        static void Main(string[] args)        {            var test = new Encoding();            Console.WriteLine(test.我就是个中文函数你拿我怎样吧());            Console.ReadKey();        }    }}namespace System.Text {    public partial class Encoding {        public string 我就是个中文函数你拿我怎样吧() {            return "有种你打我啊";        }    }}

扩展函数示例(老实说我在实际项目中没用过这玩意,这玩意虽好,但是吃螃蟹的恐惧让我并不敢用):

namespace 重构测试程序{    class Program    {        static void Main(string[] args)        {            var test = new StringBuilder();            Console.WriteLine(test.我叫扩展函数("Troy123"));            Console.ReadKey();        }    }    public static class 中文编码类    {        public static string 我叫扩展函数(this object obj, string name)//扩展函数        {            return name + "最喜欢写测试代码的时候用中文了";        }    }}

20、纯稚的数据类

  修改点:

  就是说这个类拥有一些字段,并且有一些读写这个字段的函数

  更简单的说就是不用公共字段,不要把需要隐藏的东西暴露出来(不要把你的私处暴露出来(#‵′)凸)

  解决方案:

  字段封装成方法来调用(.NET的我们用属性就好了)

  特别是对于容器类的字段要控制好它的封装,

21、被拒绝的遗赠

  修改点:

  当子类不需要继承父类多余的属性和方法时

  这个修改点在Martin看来一般并不是很严重,很多时候可以不去理睬,这个视具体情况而定

  我的理解是,如果子类继承多余的东西并没有引起误解和较差的可读性,那么其实可以不理睬

  解决方案:

  • 去为这个子类新建一个兄弟类,将这些不需要的方法和属性下移到这个兄弟类
  • 如果子类继承了馈赠,但是不愿继承父类其他的·接口,那么可以用委托来替代继承,可以参考狎昵关系

22、过多的注释

  修改点:

  这里并不是说注释不重要,而是说你这段过多的注释可能是因为你有一段超烂的代码而导致你不得不写这么多注释

  注释除了用于标注将来之外,更应该用在你那些并无把握的地方,而不是为了一段超烂的但是你理解了的代码写一段超长的注释。

  解决方案:

  • 提炼函数,用详细的函数名字去表达这段代码的意思。
  • 因为是java所以Martin还给出了java的断言去替代注释的用法,断言的意思就是如果条件不成立那么某段代码就不执行(为何我觉得如此像if,好吧这里就不深究了

 

  相关解决方案