`

重构学习笔记二(代码的坏味道)

阅读更多
代码存在需要重构的种类如下:
Duplicated Code 重复的代码
•Long Method 过长方法
•Large Class 过大类
•Long Parameter List 过长参数列表
•Divergent Change 发散式变化
•Shotgun Surgery 霰弹式修改
•Feature Envy 依恋情结
•Data Clumps 数据泥团
•Primitive Obsession 基本类型偏执
•Switch Statements Switch语句
•Parallel Inheritance Hierarchies 平行继承层次
Lazy Class 多余的类
•Speculative Generality 不确定的一般性
•Temporary Field 临时字段
•Message Chains 消息链
•Middle Man 二传手
•Inappropriate Intimacy 过度亲密
•Alternative Classes with Different Interfaces 异曲同工的类
•Incomplete Library Class 不完整的库类
•Data Class 数据类
•Refused bequest被拒绝的馈赠
•Comments 过多的注释


重复的代码
•症状:
–容易形式:两个代码段看上去几乎相同
–困难形式:两个代码段都拥有几乎相同的作用
–重复的区域:
•同一个函数内
•同一个类的不同函数内
•同一个父类的不同子类内
•不同的类中
•措施:
–抽取方法
–上移方法
–抽取类
–构造模板方法
–替换算法
•说明:
–一次,仅仅一次原则



过长的方法
•症状:
–存在大量的代码行(只要看到超过N(如10行)代码的方法,立即检查是否可以重构之。长度是一个警告信号,并不表示一定有问题。)
•措施:
–绝大多数场合下,可以采取抽取方法的重构手法来把方法变小
–每当需要以注释来说明点什么时,我们就可以把需要说明的东西写进一个独立方法中
–以其用途(而非实现方法)命名。
•其他
–在现代的开发工具中,方法调用的增多不会影响性能。
–长方法难以理解。

过大类
•症状:
–存在大量实例变量
–存在大量方法
–存在大量代码行
•原因
–不断向类中增加新的功能,逐渐变的庞大
•措施:
–抽取类
–抽取子类
–抽取接口



过长参数列表
•症状:
–方法的参数多于1个或2个
•后果:
–难以理解
–难以记忆
–容易造成前后不一致
•措施:
–将参数替换为方法
–保持对象完整性
–引入参数对象
•案例:
–Public void paintComponet(Graphicsgr,Componetrenderer,Containerparent,intx,inty, intwidth,intheight,Booleanshouldvalidate)
–取自:java.awt.Graphics


发散式变化
•症状:
–有多种变化的原因导致类的改变
•措施:
–如果类既要找到对象,又要对其做某些处理,则令调用者查找对象,并将该对象传入,或者令类返回调用者所用的值
–采用抽取类,为不同决策抽取不同的类
–如果多个类共享相同类型的决策,则可以合并这些新类。至少这些类可以构成一层。

霰弹式修改
•症状:
–发生一次改变时,需要修改多个类的多个地方
•措施:
–找出一个应对这些修改负责类,这可能是一个现有的类,也可能需要通过抽取类来创建一个新类。
–使用移动字段(move field)和移动方法(move method)将功能置于所选的类中。如果未选中类足够简单,则可以使用内联类将该类除去。


依恋情结
•症状:
–一个方法似乎过于强调处理其他类的数据,而不是处理自己的数据。
•措施:
–使用移动方法将动作置于适当的类中(可能必须先采用抽取方法分离出位置不当的部分)


数据泥团
•症状:
–同样的两至三项数据频繁地一起出现在类和参数表中
–代码声明了某些字段,并声明了处理这些字段的方法,然后又声明了更多的字段和更多的方法,如此继续。
–各组字段名以类似的子串开头或结束
•措施:
–如果项是类中的字段,则使用抽取类将其取至一个新类中
–如果值共同出现在方法的签名中,则使用引入参数对象(introduce parameter object)以抽取新的对象
–查看由新对象传递这些项的调用,以确定是否可以代之以使用保持对象完整(preserve whole object)
–查看这些项的使用:通常可以利用移动方法等重构技术,从而将这些使用移至新的对象中。


基本类型偏执
•症状:
–使用了基本类型或近基本类型(int、float、string等等)
–存在表示小整数的常量或枚举
–存在表示字段名的串常量
•措施:
–将数据值替换为对象
–将类型码替换为类
–将类型码替换为子类
–将类型码替换为状态/策略
–将数组替换为对象
•其他说明:
–用类的眼光来看待一切
•讨论:
–如何表示:电话号码、身份证号、地址?



Switch语句(模拟继承)
•症状:
–代码使用了一个switch语句,尤其是对一个类型字段
–代码在某一行上存在多个If语句,特别是对同一个值进行比较时
–代码使用了instanceof或其等价形式来确定所处理的是何类型
•措施:
–如果针对相同的一条switch语句在多处出现,它通常会使用一个类型码;将其代之以多态内置于对象中。要完成这种改变,需要采用一系列重构技术:
•1 抽取方法。抽出每个分支上的代码。
•2 移动方法。将相关代码移动到适当的类。
•3 将类型码替换为子类或将类型码替换为状态/策略。建立继承体系结构
•4 将条件式替换为多态。去除条件式。
–如果条件式出现在一个类中,可以通过将参数替换为显示方法或引入Null对象来取代条件逻辑



平行继承层次
•症状:
–在一个继承体系中建立了一个新的子类,缺发现还需要在另外一个继承体系中创建一个相关的类。
–可能发现两个继承体系中子类有相同的前缀(命令可以反映出协调继承体系的需求)
•措施:
–使用移动字段和移动方法类重新分配字段,从而可以去除某个继承体系。


多余的类
•症状
–类并没有做什么工作,似乎是由其父类、子类或者调用者完成了所有相关的工作,而在此类中却没有足够的行为,以至于对其是否继续存在会产生质疑。
•措施:
–如果一个类的父类或者子类更适合于完成该类的行为,则通过折叠继承体系将该类与其父类或子类合并。
–否则,通过内联类将其行为合并至其调用者。


不确定的一般性
•症状:
–存在未用的类、方法、字段、参数等。它们可能没有客户,或者仅有测试作为客户
–对于当前实现的需求,代码过于复杂
•措施:
–对于一个不必要的类,可以:
•折叠继承体系
•内联类
–对于一个不必要的方法,使用内联方法或移除方法
–对于一个不必要的字段,确保没有对它的任务引用,删除之
–对于一个不必要的参数,使用移除参数(remove parameter)
•说明:
–不要过度设计



临时字段
•症状:
–字段仅在某些时候得到设置,而在其余时间内为null(或未用)
•措施:
–抽取类,移除字段以及所有相关代码




消息链
•症状:
–可能看到如下的调用形式:
a.b().c().d()
•措施:
–如果处理实际上属于目标对象(即消息链最末的对象),则使用抽取方法和移动方法将处理置于该对象中。
–使用隐藏委托使方法仅依赖于一个对象(因此,不采用a.b().c().d()的形式,而是将一个d()方法置于a对象中。这可能还需要为b()和c()对象增加一个d()方法)。



二传手
•症状:
–类的大多数方法都是在调用另一个对象的同一个(或类似的)方法:
F(){delegate.f();}
•措施:
–可以通过令客户直接调用委托来移除中间人
–如果委托由中间人所有,或者是不可变的,而且中间人还有需要增加的行为,那么此中间人可以看作是委托的一个示例,如此则可以采用将委托替换为继承。



过度亲密
•症状
–一个类访问了另一个类的内部(本应是私有的)部分。
•措施
–如果两个独立的类彼此“纠缠”。使用移动方法和移动字段,将适当的部分置于适当的类中。
–如果纠缠的部分看上去是一个被遗漏的类,则使用抽取类和隐藏委托引入此新类
–如果类互相指向对方,则采用将双向引用改为单向引用,使之成为一种单向依赖
–如果子类以一种非受控的方式访问其父类的字段,则使用自封装字段
–如果父类可以定义一个通用算法,而子类能够插入其中,则使用构建模板方法
–如果父类和子类需要进一步解耦合,则采用将继承替换为委托

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics