重构 改善既有代码的设计--笔记

Large Class(过大的类)

查看一个类是否“过大”,这里有一个小技巧分享给大家。就是看两点:1)这个类实例变量太多,必然会有Duplicated Code(重复代码) 2)类内如果有太多代码,也会产生Duplicated Code,让整个类看起来混乱并最终走向死亡。

因此当你察觉到这个类是一个Large Class的时候,重构的信号就来了。对于实例变量太多来说,你可以查看寻找那些彼此相关的变量,或者是他们的命名的前缀或者后缀是相同的变量,你可以通过Extract Class把他们移到别的组件中去。如果这个组件你感觉更加是否作为本类的子类,那么你可以运用Extract Subclass来进行提炼。

如果你发现有些时候,类中并非在所有时刻都使用所有实例变量,那么你可以多次使用Extract Class或者Extract Subclass。文章作者提供了一个提炼的小技巧,你如果不知道如何提炼分解这个类,你可以去查看客户端是如何使用他们的,然后通过Extract Interface提炼接口,借助于这些提炼接口,可以帮助你知道如何更好的分解这个过大类。

如果你的过大类是一个GUI类,那么你可能需要将数据和行为移动到一个独立的领域中去。并且你需要GUI和数据行为两者的数据保持同步。那么你可以使用Duplicate Observed Data来进行提炼。

Long Parameter List(过长参数列)

在对象技术出来之前,函数的参数列表往往是又臭又长,然而有时候你如果不想要这种长参数列的函数你得去依靠全局变量这种邪恶的东西。而且在我们一开始学习编程的时候老师就教导我们,函数需要什么你就在参数列表中写什么。是时候需要改变了,对象技术的出现提供了我们改变这一现状的手段。你不再需要传过长过大的参数列,因为太多参数往往会造成参数前后不一致,不易使用,更重要的是一旦你需要更多的数据,你就不得不去修改它。相反如果你通过传入对象,首先你的参数列表就很短,其次如果你想增加别的变量,会有可能只需要在函数中对这个参数对象多加一次请求就行了。

如果向已有的对象发送一条请求可以取代一个参数,那么你应该使用Replace Parameter with Method。注意是已有的参数,不是不存在的参数。这个需要理解一下,已有的参数就是函数宿主类中的某一个对象字段,也可能是函数本身存在另一个对象参数,让这个对象来替换它。如果某些数据缺乏合理的对象归属。可以使用Introduce Parameter Object来为它们制造一个“参数对象”。

有些同学可能会和我对这条有同样的疑问,有些时候我们不想增加对象与对象之间的关联。不想让被调用对象与较大对象之间有某种依赖关系。这个时候将数据从对象拆解出来单独作为参数也合情合理。但如果你此时函数的参数列表过长或者变化太频繁,你确实应该采取本手法来进行重构。

Divergent Change(发散式变化)

我们需要软件更容易被修改。遇到修改,我们希望只跳到系统的某一点,只在该处做出修改就行。但情况往往没有这么简单,因为总有那么几个类变化的原因往往是多个,他们经常会因为不同的原因在不同的方向上都要变化,都要做出适当修改。这个时候你就要注意了,比如你看到一个类说如果加入一个数据库,我需要修改其中的三个函数。如果加入一个金融工具,我需要修改其中的四个函数。面对这两个不同工具的加入,你有2个方向上的变化。那么其实你更应该用Extract Class将这个类分成两个类,每个类只针对一个变化原因进行变化,就比如类A只对数据库进行变化,类B只对金融工具发生变化。为此,你应该找出某特定原因而发生的所有变化,然后Extract到别的class中去。时刻要记住这么一句话:针对某一外界变化的所有相应修改,都应该产生在单一类中,而这个新类中的所有内容都应该反应此变化。

Shotgun Surgery(散弹式修改)

情况与Divergent Change类似,但不同点在于,Divergent Change是指一个类受多种变化的影响,而Shotgun Surgery表示针对某一变化,你都必须在不同类做出相应的修改。针对这种情况你需要使用Move Mehod和Move Field将需要修改的代码放进同一个类中,如果眼下没有合适的类的话,就创建一个。通常你也可以使用Inline Class把一系列相关行为放进同一个类,这可能会造成少量Divergent Change,但你可以轻易处理它。

Shotgun Surgery和Divergent Change你都需要适时整理重构代码,让“外界变化”和“需要修改的类”趋于一一对应。

Feature Envy(依赖情结)

面向对象技术就是将数据和行为包装在一起。一个经典的坏味道的场景就是函数都某个类的兴趣高过对自己所处类的兴趣,往往焦点就是数据。很多时候我们可以看到这种场景,类A的中的函数为了进行计算获取了类B中几乎一半的数据,面对这种情况,其实很简单,就是使用Move Method将这个函数直接移到B中去,然后让类A的调用点就调用类B的这个函数。如果一个函数中,只有一部分受这种“依恋之苦”,你应该用Extract Method把这一部分提炼出来,然后通过Move Method把这个提炼的函数移动到他所依恋的类中去。

如果出现一个函数需要用到几个类的时候,我们会很难判断究竟应该把它放哪。这个时候有个小技巧你只要记住,这个函数获取哪个类的数据最多,就把这个函数移动到哪个类中去。当然面对这种多重以来,你也可以用Extract Method将这个函数分解成一系列小函数然后移动到他们对应的需要的类中去也可以轻松完成。

文中作者也提到了设计模式GoF也有破坏这个规则的时候,我们一起来看下:Stategy和Visitor,他们通过一个间接层,将实现委托给了Stategy和Visitor,而不是直接去访问这个间接层,这样,原来函数的数据就与真正需要他们的Stategy和Visitor中的行为分开了。那么他们的目的是什么?可以说这两个模式主要是为了解决Divergent Change而设计的。总之最根本的原则就是:将总是一起变化的东西放在一块儿,数据和引用这些数据的行为总是一起变化的。但也有例外,如果例外出现,我们就搬移动那些行为,保持变化只在一个地方发生,Strategy和Visitor使你得以轻松修改函数行为,因为他们将少量需要被覆写的行为隔离开来,当然也付出了“多一层间接性”的代价。

原文发布于微信公众号 - java一日一条(mjx_java)

原文发表时间:2018-08-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术栈

跟我学 Java 8 新特性之 Stream 流(四)并行流

在开始讨论并行流之前,我先引发一下大家的思考,就你看到这篇文章的时间,你们是不是经常听到,Intel i7 CPU什么8核16线程,什么Android手机8核4...

922
来自专栏Android 开发者

[译] Kotlin 揭秘:理解并速记 Lambda 语法

在奥地利旅行期间,我参观了维也纳的奥地利国家图书馆。特别是国会大厅,这个令人惊叹的空间感觉就像印第安纳琼斯电影中的一些东西。房间周围的空间是这些门被装在架子上,...

870
来自专栏阿杜的世界

《重构》阅读笔记-代码的坏味道

开发者必须通过实践培养自己的经验和直觉,培养出自己的判断力:学会判断一个类内有多少个实例变量算是太大、学会判断一个函数内有多少行代码才算太长。

742
来自专栏java工会

深度思考编程的艺术

1758
来自专栏静默虚空的博客

如何学习一门编程语言

前言 很多人喜欢争论什么什么编程语言好,我认为这个话题如果不限定应用范围,就毫无意义。 每种编程语言必然有其优点和缺点,这也决定了它有适合的应用场景和不适合的应...

4205
来自专栏架构师之路

如何设计好的接口(Google分享)

本文源自Google工程师joshua bloch的经验分享,楼主进行了整理和总结。 一、好接口的特性 (1)易学 (2)易用,甚至不需要文档 (3)难于误用 ...

3816
来自专栏java一日一条

哪些因素影响Java调用的性能?

这得从一个小故事说起。我在一个Java核心库的邮件列表中提交了一个修改 ——重写了一些本是 final 的方法。一石激起千层浪,这一改动引发了几番讨论。而其中一...

1201
来自专栏编程

浅谈Java学习方法和后期面试技巧 含学习笔记

下面简单列举一下大家学习java的一个系统知识点的一些介绍: 一、java基础部分:java基础的时候,有些知识点是非常重要的,比如循环系列。For,while...

2018
来自专栏C语言及其他语言

[每日一题]选择法排序

谈到排序的方法,可以说是多种多样,比较常用的是冒泡法,而效率比较高的是快速法,今天给大家介绍的则是选择法 题目描述 用选择法对10个整数从小到大排序。 输入 ...

2966
来自专栏维C果糖

编程思想 之「语言导论」

Java 是一门面向对象编程语言,它不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此 Java 语言具有功能强大和简单...

45519

扫码关注云+社区

领取腾讯云代金券