24种坏味道(重构2)

  1. 神秘命名(Mysterious Name)

    1. 整洁代码最重要的一环就是好的名字。
    2. 如何给函数、模块、变量和类定义一个清晰地并能表明自己的功能和用法的命名,能够让代码直观明了,是编程中最难得两件事之一
    3. 改名也可能是重构中最常用的手法,包含:改变函数声明、变量改名、字段改名
  2. 重复代码(Duplicate Code)

    1. 如果你在一个以上的地点看到相同的代码结构,那么设法将他们合而为一,程序会变得更好
    2. 同一个类的两个函数具有相同的表达式,可以采用提炼函数方法提炼出重复的代码
    3. 如果重复代码只是相似而不完全相同,尝试使用移动语句重组代码顺序,把相似部分放在一起,以便提炼
    4. 如果重复的代码位于同一个超类的不同子类中,可以使用函数上移来避免在两个子类间相互调用
  3. 过长函数(Long Function)

    1. 比较短的函数,都是最好的程序,活的最长。函数越长,越难理解
    2. 每当感觉需要以注释来说明点什么的时候,就需要把说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。
    3. 不管是一组还是一行,不管是命名长还是函数长,只要函数名称能解释用途,我们也要做。在于函数“做什么”和“如何做”之间的语义距离
    4. 99%场合,要把函数变短,只需使用提炼函数方法,找到函数中适合集中在一起的部分,将它们提炼成一个新函数
    5. 如果函数内存在大量的参数和临时变量,你可以经常运用以查询取代临时变量来消除临时元素。引入参数对象保持对象完整将过长的参数列表变整洁
    6. 如果还是有太多临时变量和参数,就使用以命令取代函数
    7. 如何确定该提炼哪一段代码呢?可以使用一个很好的技巧:寻找注释
    8. 条件表达式和循环也是提炼的信号,可以使用分解条件表达式处理条件表达式
    9. 对于庞大的switch语句,其中每个分支都应该通过提炼函数变成独立的函数调用,如果有多个switch语句基于同一个条件进行分支选择,就应该使用以多态取代条件表达式
    10. 对于循环,你应该将循环和循环内的代码提炼到一个独立的函数中,如果还是很难命名,可能是因为做了几件不同的事。请不要考虑性能,勇敢地使用拆分循环方法
  4. 过长参数列表(Long Parameter List)

    1. 如果可以向某个参数发起查询而获得另一个参数的值,可以使用以查询取代参数,去掉这第二个参数
    2. 如果你发现自己正在从现有的数据结构中抽取很多数据项,可以考虑使用保持对象完整手法,直接传入原来的数据结构
    3. 如果有几项参数总是同时出现,可以用引入参数对象将其合并为一个对象
    4. 如果某个参数被用作区分函数行为的标记,可以使用移除标记参数
    5. 使用类可以有效地缩短参数列表,如果多个函数有相同的几个参数,可以使用函数组合成类,将共同的参数变成这个类的字段
  5. 全局数据(Global Data)

    1. 全局数据最显而易见的形式就是全局变量,类变量和单例
    2. 首要的防御手段是封装变量,把全局数据用一个函数包起来,可以看见修改它的地方,并开始控制对它的访问
    3. 最好将这个函数移到一个类或模块中,只允许模块内的代码使用它,从而尽量控制其作用域
  6. 可变数据(Mutable Data)

    1. 可以使用封装变量来确保所有数据更新操作都通过很少几个函数来进行,使其更容易监控和演进
    2. 如果一个变量在不同时候被用于不同的概念,可以使用拆分变量将其拆分为各自不同用途的变量
    3. 使用移动语句提炼函数尽量把逻辑从处理更新操作的代码中移出来,将没有副作用的代码和执行数据更新操作的代码分开
    4. 设计API时,可以使用将查询函数和修改函数分离确保调用者不会调到有副作用的代码,除非他们真的要更新数据
    5. 我们还乐于尽早使用移除设值函数——有时只是把设值函数的使用者找出来看看,就能帮我们发现缩小变量作用域的机会。
    6. 如果可变数据的值能在其他地方计算出来,这是毫无必要的,必须使用以查询取代派生变量替换
    7. 使用函数组合成类或者函数组合成变换来限制需要对变量进行修改的代码量
    8. 如果一个变量在其内部结构中包含了数据,通常不要直接修改其中的数据,而是用将引用对象改为值对象令其直接替换整个数据结构
  7. 发散式变化(Divergent Change)

    1. 如果某个模块经常因为不同的原因在不同的方向或功能上发生变化,“每次只关心一个上下文”这一点一直很重要。(类似于单一职责)
    2. 如果发生变化的两个方向自然地行成了先后次序(比如先从数据库取出数据,再对其进行逻辑处理),就可以用拆分阶段将两者拆开,两者之间通过一个清晰地数据结构进行沟通
    3. 如果两个方向之间有更多的来回调用,就应该先创建适当的模块,然后用搬移函数把处理逻辑分开
    4. 如果函数内部混合了两类处理逻辑,应该先用提炼函数将其分开,然后再做搬移
    5. 如果模块是以类的形式定义,就可以用提炼类来做拆分
  8. 霰弹式修改(Shotgun Surgery)

    1. 如果每次遇到某种变化,你都必须在许多不同的类内作出许多小修改,如果需要修改的代码散步四处,不但很难找到,也很容易错过某个重要的修改
    2. 应该使用搬移函数搬移字段把所有需要修改的代码放进同一个模块里,如果有很多函数都在操作相似的数据,可以使用函数组合成类
    3. 如果有些函数的功能是转化或者充实数据结构,可以使用函数组合成变换
    4. 如果一些函数的输出可以组合后提供给一段专门使用这些计算结构的逻辑,可以使用拆分阶段
    5. 面对霰弹式修改,一个最常用的策略就是使用与内联相关的重构——如内联函数或是内联类——把本不该分散的逻辑拽回一处
  9. 依恋情节(Feature Envy)

    1. 所谓模块化,就是力求将代码分出区域,最大化区域内部的交互,最小化跨区域的交互,但有时,一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于自己所处模块内部的交流
    2. 这个函数想跟这些数据待在一起,那就使用搬移函数把它移过去。如果函数中只有一部分受这种依恋之苦,这时应该使用提炼函数把这一部分提炼到独立的函数中,再使用搬移函数
    3. 一个函数往往会用到几个模块的功能,我们的原则是:判断哪个模块拥有的此函数使用的数据最多,然后就把这个函数和那些数据摆在一起。可以使用提炼函数将这个函数分解为数个较小的函数并分别放置于不同地点
  10. 数据泥团(Data Clumps)

  11. 基本类型偏执(Primitive Obsession)

  12. 重复的Switch(Repeated Switches)

    1. 在20世纪90年代末期,程序员太过于忽视多态的价值。所以遇到任何switch语句都会提议以多态取代条件表达式消除该语句
    2. 如今的程序员已经更多地使用多态,switch也不像15年前那样有害无益,很多语言支持更复杂的switch语句,因此,我们现在更关注重复的switch语句,在不同的地方反复使用同样的switch逻辑
    3. 重复的switch问题在于:每当你想增加一个选择分支时,必须找到所有的switch,并逐一更新,多态给了我们对抗黑力量的武器
  13. 循环语句(Loops)

    1. 从最早的编程语言开始,循环就一直是程序设计的核心要素。但如今循环有点过时。
    2. 如今,函数座位一等公民已经得到广泛支持,因此我们可以使用以管道取代循环来让这些老古董退休
  14. 冗赘的元素(Lazy Element)

  15. 夸夸其谈通用性(Speculative Generality)

  16. 临时字段(Temporary Field)

  17. 过长的消息链(Message Chains)

  18. 中间人(Middle Man)

  19. 内幕交易(Insider Trading)

  20. 过大的类(Large Class)

  21. 异曲同工的类(Alternative Classes with Different Interfaces)

    1. 使用类的好处之一就在于可以替换,今天用这个类,未来可以换成用另一个类。但只有当两个类的接口一致时,才能做这种替换
    2. 可以用改变函数声明将函数名变得一致,但这往往不够,反复运用搬移函数将某些行为移入类中,直到两者的协议一致为止
    3. 如果搬移过程中造成了重复代码,或许可运用提炼超类补偿一下
  22. 纯数据类(Data Class)

  23. 被拒绝的遗赠(Refused Bequest)

  24. 注释(Comments)

    1. 别担心,注释并不是一种坏味道,反而是一种香味,但是因为人们常常把它当做除臭剂来使用:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在是因为代码很糟糕。
    2. 注释可以带我们找到前面提到的各种坏味道,找到坏味道后,我们首先应该以各种重构手法把坏味道去除。完成之后我们常常会发现:注释已经变得多余,因为代码已经清楚的说明了一切

posted on 2023-01-07 18:16  一直工作的小白鼠  阅读(186)  评论(0编辑  收藏  举报

导航