第3章 代码的坏味道

重构的判断基础:

    能知道什么时候需要重构,什么地方需要重构,是需要一定的判断力,下面列出的22条“坏味道”条款,可能会成为判断的基础。

 

一、重复代码

如果在一个以上地方看到相同的程序结构,那么可以肯定,将它们合二为一,程序会更好。

1.能举一个简单的例子说明什么是重复代码么?

答:一个最常见的情况,就是在同一个类里,有两个方法中都有相同的表达式,那么这个表达式就可以被抽出来,封装起来供那两个方法调用。

2.能举一个你自己的例子么?

答:在之前设计模式读书笔记的设计原则总结中我有提到过,在以前的项目中,我设计一个复杂类型数据的更新和插入,都是写在用了同一个方法里,不管更新还是插入都调用这个方法,但是导致了耦合度过高,违反了单一职责原则,不利于后续的扩展,于是当时只是想着这样处理是错误的,应该把他们拆开,虽然会导致很多重复代码,但是拆开是有必要的。现在在了解了重构之后,我觉得虽然有一些细节上是不同的,但是拆开插入和更新方法的同时,他们数据的构建大部分是一致的,所以可以把数据构建的方法提出来,这样既可以满足单一职责原则,又可以避免重复代码。

考虑到更细和插入的数据大部分相同,就将组装相同的数据提到方法dataBuild()里面,然后些许不同的地方就分别构建,新增数据的构造就放在addDataBuild()里,更新数据的构造就放在updateDataBuild()里,然后在新增数据就直接调用方法add(),更新数据就直接调用update()

新增数据 →  add()   →  addDataBuild()   → dataBuild()
更新数据 → update() → updateDataBuild() → dataBuild()

 

二、方法过长

拥有短函数的对象会活的比较久,比较长。

1.怎么判断是不是方法过长,有什么原则么?

答:你应该积极分解函数,我们遵循这样一条原则:每当感觉需要用注释来说明点什么的时候,我们就需要把说明的东西写进单独的函数里,并以其用途(而非实现手法)命名。

      且原文中写道(目前还不是能特别理解这句,会不会太绝对了,这样不会产生很多细碎的方法调用么?):我们可以对一组甚至短短一行代码做这件事,哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。

2.如何确定该提炼哪一段代码呢?

答:一个很好地技巧,即寻找注释。它们通常能指出代码用途和实现手法之间的语义距离,用注释不如提出来封装到函数中,用函数命名说明。另外条件表达式循环也是提炼的信号。

 

三、过大的类

类中如果有太多的代码,也是代码重复,混乱,并最终走向死亡的源头。

1.当类过大时,怎么提取代码?

答:有个技巧,即先确定客户端如何使用它们,然后为每一种使用方式提炼出一个接口,这或许可以帮你看清楚如何分解这个类。

 

四、过长的参数列

如果传入一个方法的参数过多过长,请传入一个对象代替。

1.那入参我都封装成一个对象可以么,有例外么?

答:一两个参数还是没有必要,或者有时候你明显不希望造成“被调用对象”与“较大对象”间的某种依赖关系,这时将数据从对象中单独拆解出来作为参数也是合情合理的。

 

五、发散式变化

1.什么叫发散式变化?

答:发散式变化是指:某个类经常因不同的原因在不同的方向上发生变化。出现这种情况时,那么就应该考虑重构了。

2.能举个例子么?

答:比如新的需求是加入一个数据库,然后这个类有三个方法需要修改,然后另一个新需求是加入一个监控工具,这时这个类有其他四个方法需要修改,这时就应该考虑将这些方法分开,将这个类拆成两个类。

 

六、散弹式修改

1.那什么叫散弹式修改?

答:每次遇到某种变化,你都需要在许多不同的类做出许多小的修改,这就是散弹式修改。

2.遇到这种情况怎么重构?

答:将所有需要修改的代码放进一个类,如果目前没有合适的类来存放,那么就新建一个类。将这些方法提到一个类之后,可能又会导致些许的发散式修改,但你可以轻易修改它。

 

七、依恋情节

对象技术的全部要点在于:这是一种“将数据和对数据的操作行为包装在一起”的技术。

1.依恋?

答:也就是说,一个类中的方法,对另一个类的兴趣高于对自己类的兴趣,这种兴趣的程度怎么判断呢,一般是通过数据来体现的。

2.举个例子

答:Class A中的方法calculateAmount(),这个方法在进行计算总额是,用了许多Class B中的取值方法,显而易见,这个方法应该移动到Class B中。

3.一个方法用到几个类中取值调用,那又怎么移动呢?

答:可以看哪个类拥有最多被这个方法使用的数据,就移动到哪个类中。

 

八、数据泥团

1.取名小天才请告诉我什么又叫数据泥团?

答:举个例子来理解,比如有两个类中有着相同的字段,定义着一些相同的参数,那么这些数据应该拥有自己的类,或者说拥有自己的对象。

2.如果抽象出了一个对象有3个字段,A类有2个相同字段,B类有2个相同字段,C类只有1个相同字段,那么这个对象还值得单独提出么?

答:只要用一个对象取代大于等于两个对象,就是赚了。

 

九、基本类型偏执

可以将一些基本数据类型替换为对象,将数值和币种结合成money类,初始值和结束值结合成range类等,走进对象的世界。

 

十、switch惊悚现身

1.为什么要把switch statement翻译成“switch惊悚现身”?

答:梦里不知身是客,一晌贪欢。

2.为什么少用switch?

答:switch的问题在于重复,每次新加一个switch中case的子类,就要找到相应的switch语句,找到相应点并添加,如果有很多switch语句,就要全部找到的并依次添加。

3.怎么减少?

答:使用多态,但有时候使用多态又会显得杀鸡用牛刀,这时可以以明确的函数代替参数。

 

十一、平行继承体系

1.什么是平行继承体系?

答:当增加一个类的子类时,还必须为另一个类增加一个子类,且这两个继承体系的类名称前缀是相同的时。 

2.举个例子

答:比如小诚喜欢打王者荣耀,他在买了苹果手机后,安装了游戏,这时特朗普突然禁止和微信的交易,那不是不能买皮肤了?小诚又只好再买了一个手机,手机是华为手机,那么华为手机就算是手机的子类,在华为手机继承手机的时候,还需要新增一个王者荣耀游戏子类继承游戏类。

3.怎么消除这种坏味道?

答:让一个继承体系的实例,引用另一个继承体系的实例,如:

参考桥接模式:https://www.cnblogs.com/wencheng9012/p/13445419.html

 

十二、冗赘类

无用即删

 

十三、夸夸其谈未来性

1.啥意思?

答:即:“哦,我想我们总有一天会做这件事”的这种代码

2.举个例子

答:比如多余的参数,本来只需要构造两个参数,时间和地点,但你却想到了以后可能会用到的参数,然后也一同加上了:人物,行为等,这些参数对目前没用。

3.这样有什么坏处?

答:使代码难以理解和维护,也不值得。

 

十四、临时字段

1.什么叫临时字段?

答:某个实例变量仅为代码中一小部分功能临时所用而创建。

2.有什么坏味道?

答:可读性差。

3.解决方法?

答:将所有临时字段放在一起。

 

十五、过度耦合的消息链

1.什么是消息链?

答:用户向一个对象请求另一个对象,另一个对象又请求下一个对象,然后再请求另一个对象...这就是消息链。

2.坏味道?

答:可能一旦对象间的关系出现任何变化,客户端就不得不做出相应修改,耦合度过高,不利于修改和维护。

3.怎么解决?

答:通常使用隐藏委托关系,在重构前先观察消息链最终得到的对象是用来干什么的,看看能否用抽象方法,把该对象的方法提炼到一个函数中,再用搬运函数把函数推入消息链中。

4.什么是隐藏委托关系?

即:万一委托关系发生变化,客户也得相应变化,如图中图中需要得到经理时:Client.getDepartment.getManager()。你可以在server端(如这里的:Person)放置一个简单的委托函数(delegating method),将委托关系隐藏起来,从而去除这种依存性。这么一来即便将来发生关系上的变化,变化将被限制在server中,不会涉及客户。

 

十六、中间人

1.概念

答:对象的基本特征之一就是封装,封装往往伴随着委托,但有时候会过度运用委托,当你看到某个类的一个接口一大半的方法都委托给了其他类,或者每当客户要使用受托类的新特性时,你就必须在服务端添加一个简单委托函数。随着委托类的特性(功能)越来越多,这一过程让你痛苦不已。服务类完全变成了“中间人”,这就是过度运用,此时你就应该让客户直接调用受托类。

2.重构

答:让客户端直接调用受托类。

 

十七、狎昵关系

两个类如果过于亲密,花太多时间去探究各自private成分,就如继承往往会导致过度亲密,则希望让这个子类离开继承体系,把两个类分开,划清界限。

 

十八、异曲同工的类

两个功能相同的类,却有着不同的签名,要么根据真正用途重新命名, 要么去二存一,融为一体。

 

十九、不完美的库类

封装好的类库中功能不能满足实际需求,通过引入外加函数或者引入本地扩展解决。

 

二十、纯稚的数据类

一个对象应该包括它的属性及对其的操作方法,对特定数据的操作应集中在一个地方,而不是随意存放。把相应的方法搬移到对应的类中后,对类中对应的在外部没有被使用过的get/set方法设置成私有方法。

 

二十一、被拒绝的遗赠

1.概念

答:继承某个类的子类,不需要父类的某些方法,或属性,或实现父类的实现的接口。

2.解决

答:看收益,消除不必要的继承,组合优于继承,或使用代理取代继承。

 

二十二、过多的注释

一是有时候不是需要注释,而是需要修改代码,代码可读性提高了,那么注释也就不需要了,二是有些陈旧的注释如果没有随着代码的改变而改变,又会影响可读性。

1.建议:

答:当你感觉要写注释时,请先尝试重构,试着让所有注释变得多余。

 

疑问点小结:

1.原文如下,我感觉有点太绝对了,这样会产生很多细碎的方法调用,或者可能在我现在的认知里,有很多需要加的注释是没有必要的,那怎么把握这个度也是我要学习和进步的一个点。

  “我们可以对一组甚至短短一行代码做这件事,哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。”

  进度:未解决。

 

2.在解决散弹式修改时提到,如果几次变化都是同一个类型,且这个变化都要引起部分类的修改,那么就应该把这些方法单独提到一个类中,但是可能又会造成发散式变化的坏味道,然后作者说:“会造成少量的发散式变化,但你可以轻易修改它。”这个轻易修改是怎么修改,先提取到一个类再进行拆分么?

  进度:未解决。

posted @ 2020-08-17 14:48  pmingup9012  阅读(287)  评论(0编辑  收藏  举报