代码的坏味道「上」
决定何时重构及何时停止和知道重构机制如何运转一样重要!\
必须培养自己的判断力,学会判断一个类内有多少实例变量算是太大,一个函数内有多少行代码才算太长。
1. 神秘命名
整洁代码最重要的一环就是好的名字,所有我们会深思熟虑如何给函数,模块,变量和类命名,是他们能清晰的表明自己的功能和用法;
- 好的名字能节省未来用在猜谜上的大把时间;
- 如果想不出一个好名字,说明背后很可能潜藏着更深的设计问题;
- 可以采用 「改变函数命名」,「变量改名」,「字段改名」等方法;
2. 重复代码
-
如果在一个以上的地点看到相同的代码结构,那么可以肯定:设法将它们合而为一,程序会变得更好;
-
一旦有重复的代码存在,阅读这些重复的代码时你就必须加倍仔细,留意期间细微的差异,。如果要修改重复代码,必须找出所有的副本来修改;
-
可以采用
- 「提炼函数」提炼出重复的代码,然后让这两个地方都调用被提炼出来的一段代码;
- 「移动语句」:如果重复代码只是相似而不是完全相同,首先使用 移动语句的方法,重组代码顺序,把相似的部分放在一起以便提炼;
- 「函数上移」:如果重复代码短位于同一个超类的不同子类中,可以使用 函数上移 来避免在两个子类之间互相调用;
3.过长函数
活得最长,最好的程序,其中的函数都比较短;
- 拆分成简短的函数的价值在于:间接性带来的好处--- 更好的阐释力,更易于分享,更多的喧选择;
函数越长,越难理解;让小函数易于理解的关键在于良好的命名;
-
如果可以给函数起一个好名字,阅读代码的人就可以通过名字了解函数的作用,根本不必去看其中写了什么;
-
最终的效果时:应该更积极的分解函数,遵循一条原则:每当感觉需要以注释来说明点什么的时候,就把需要说明的东西写进一个独立的函数中,并以其作用命名,而不是实现手法命名;
-
重构方法:
- 要把函数变短,只需使用 「提炼函数」,找到函数中适合集中在一起的部分,将他们提炼出来形成一个新函数;
- 在提炼函数时,如果把许多参数传递给被提炼出来的新函数中,导致可读性没有任何提升,此时,可以运用 「以查询取代临时变量」 来消除这些临时变量;
- 「引入参数对象」,「保持对象完整」 可以将过长的参数裂变变得更简洁;
如何确定该提炼哪一段代码呢?一个技巧时:寻找注释;\
如果代码前方有一行注释,则可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。- 条件表达式的提炼:可以使用 「分解表达式」 处理条件表达式,对于庞大的
switch
语句给予同一个条件进行分支选择,就应该使用 「以多态取代表达式」; - 循环的提炼:应该将循环和循环内的代码提炼到一个独立的函数中,如果发现提炼出的函数很难命名,可能是因为其中做了几件不同的事,如果是这种情况,请使用 「拆分循环」 将其拆分成各自独立的任务;
4. 过长参数列表
- 如果可以向某个参数发起查询而获得另一个参数的值,就可以使用 「以查询取代参数」 去掉第二个参数;
5. 全局数据
- 全局数据的问题在于:从代码的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出来修改;
- 全局函数最显而易见的形式及时全局变量,类变量和单利也有这样的问题
- 重构方法:
- 首要的防御手段就是 「封装变量」 每当看到可能被各处的代码污染的数据,这总是我们应对的第一招;把全局数据用一个函数包装起来,就可以控制对它的访问;
6. 可变数据
对数据的修改经常导致出乎意料的结果和难以发现的 bug;
- 如果要更新一个数据结构,就返回一份新的副本,旧的数据仍保持不变;
- 重构方法:
- 可以用 「封装变量」 来确保所有数据更新操作都通过很少几个函数来进行,使其更容易监控和演进;
- 如果一个变量在不同时候被用于存储不同的东西,可以使用 「拆分变量」 将其拆分为各自不同用途的变量,从而避免危险的更新操作;
- 使用 「移动语句」 和 「提炼函数」 尽量把逻辑从处理更新操作的代码中搬移出来,将没有副作用的代码与执行数据更新的代码分开;
- 设计 API 时,可以使用 「将查询函数和修改函数分离」 确保调用者不会调到有副作用的代码;
- 如果可变数据的值能在其他地方计算出来,使用 「查询取代派生变量」来重构;
7. 发散式变化
如果某个模块经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了;
- 如果增加一个新功能,需要对原有功能做出大量的修改。这就是散发是变化的征兆
- 每当要对某个上下文做修改时,只需要理解这个上下文,而不必操心另一个,“每一次只关系一个上下文”这一点很重要;
- 重构方法:
- 如果发生变化的两个方向自然地形成先后次序,可以用 「拆分阶段」 将两者分开;
8. 霰弹式修改
如果修改的代码散步四处,不但很难找到他们,也很容易错过某个重要的修改;
- 重构方法:
- 应该使用 「搬移函数」 和 「搬移字段」 把所有需要修改的代码放进同一个模块里;
- 面对霰弹式修改,一个常用的策略就是使用与内联相关的重构---如 「内联函数」 或者 「内联类」,把本不该分散的逻辑拽回一处;
9. 依恋情结
所谓模块化,就是力求将代码分出区域,最大化区域内部的交互,最小化跨区域的交互。
- 有时会发现,一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所有的模块内部的交流,这就是典型的依恋情况;
- 重构方法:
- 这个函数想跟这些数据待在一起,就可以使用 「搬移函数」 把它移过去。
- 有时候函数中只有一部分受这种依恋之苦,这时应该使用 「提炼函数」 把这部分提炼到独立的函数中,再使用 「搬移函数」 ;
判断哪个模块拥有的词函数使用的数据最多,然后就把这个函数和那些数据摆在一起;
10. 重复的 switch
任何
switch
语句都应该用 「以多态取代条件表达式」 消除掉,所有条件逻辑都应该用多态取代,绝大多熟if
语句都应该被消除掉;
- 在不同的地方反复使用同样的
switch
逻辑,重复的switch
的问题在于:每当你想增加一个选择分支时,必须找到所有的switch
,并逐一更新。多态可以帮我们写出更优雅的代码库;
11. 循环语句
函数作为一等公民已经得到了广泛的支持,因为可以使用 「以管道取代循环」 来让循环消失。
- 「管道操作(filter/map)」 可以帮助我们更快地看被处理的元素以及处理它们的动作;
本作品采用《CC 协议》,转载必须注明作者和本文链接