Monkey

个人站

The master has failed more times than the beginner has tried


代码的坏味道「上」

决定何时重构及何时停止和知道重构机制如何运转一样重要! 必须培养自己的判断力,学会判断一个类内有多少实例变量算是太大,一个函数内有多少行代码才算太长。

1. 神秘命名

整洁代码最重要的一环就是好的名字,所有我们会深思熟虑如何给函数,模块,变量和类命名,是他们能清晰的表明自己的功能和用法;

  • 好的名字能节省未来用在猜谜上的大把时间;
  • 如果想不出一个好名字,说明背后很可能潜藏着更深的设计问题;
  • 可以采用 「改变函数命名」,「变量改名」,「字段改名」等方法;

2. 重复代码

  • 如果在一个以上的地点看到相同的代码结构,那么可以肯定:设法将它们合而为一,程序会变得更好;

  • 一旦有重复的代码存在,阅读这些重复的代码时你就必须加倍仔细,留意期间细微的差异,。如果要修改重复代码,必须找出所有的副本来修改;
  • 可以采用
    • 「提炼函数」提炼出重复的代码,然后让这两个地方都调用被提炼出来的一段代码;
    • 「移动语句」:如果重复代码只是相似而不是完全相同,首先使用 移动语句的方法,重组代码顺序,把相似的部分放在一起以便提炼;
    • 「函数上移」:如果重复代码短位于同一个超类的不同子类中,可以使用 函数上移 来避免在两个子类之间互相调用;

3.过长函数

活得最长,最好的程序,其中的函数都比较短;

  • 拆分成简短的函数的价值在于:间接性带来的好处— 更好的阐释力,更易于分享,更多的喧选择;

函数越长,越难理解;让小函数易于理解的关键在于良好的命名;

  • 如果可以给函数起一个好名字,阅读代码的人就可以通过名字了解函数的作用,根本不必去看其中写了什么;
  • 最终的效果时:应该更积极的分解函数,遵循一条原则:每当感觉需要以注释来说明点什么的时候,就把需要说明的东西写进一个独立的函数中,并以其作用命名,而不是实现手法命名;
  • 重构方法:
    • 要把函数变短,只需使用 「提炼函数」,找到函数中适合集中在一起的部分,将他们提炼出来形成一个新函数;
    • 在提炼函数时,如果把许多参数传递给被提炼出来的新函数中,导致可读性没有任何提升,此时,可以运用 「以查询取代临时变量」 来消除这些临时变量;
    • 「引入参数对象」,「保持对象完整」 可以将过长的参数裂变变得更简洁;

    如何确定该提炼哪一段代码呢?一个技巧时:寻找注释; 如果代码前方有一行注释,则可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。

    • 条件表达式的提炼:可以使用 「分解表达式」 处理条件表达式,对于庞大的 switch 语句给予同一个条件进行分支选择,就应该使用 「以多态取代表达式」;
    • 循环的提炼:应该将循环和循环内的代码提炼到一个独立的函数中,如果发现提炼出的函数很难命名,可能是因为其中做了几件不同的事,如果是这种情况,请使用 「拆分循环」 将其拆分成各自独立的任务;

4. 过长参数列表

  • 如果可以向某个参数发起查询而获得另一个参数的值,就可以使用 「以查询取代参数」 去掉第二个参数;

5. 全局数据

  • 全局数据的问题在于:从代码的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出来修改;
  • 全局函数最显而易见的形式及时全局变量,类变量和单利也有这样的问题
  • 重构方法:
    • 首要的防御手段就是 「封装变量」 每当看到可能被各处的代码污染的数据,这总是我们应对的第一招;把全局数据用一个函数包装起来,就可以控制对它的访问;

6. 可变数据

对数据的修改经常导致出乎意料的结果和难以发现的 bug;

  • 如果要更新一个数据结构,就返回一份新的副本,旧的数据仍保持不变;
  • 重构方法:
    • 可以用 「封装变量」 来确保所有数据更新操作都通过很少几个函数来进行,使其更容易监控和演进;
    • 如果一个变量在不同时候被用于存储不同的东西,可以使用 「拆分变量」 将其拆分为各自不同用途的变量,从而避免危险的更新操作;
    • 使用 「移动语句」 和 「提炼函数」 尽量把逻辑从处理更新操作的代码中搬移出来,将没有副作用的代码与执行数据更新的代码分开;
    • 设计 API 时,可以使用 「将查询函数和修改函数分离」 确保调用者不会调到有副作用的代码;
    • 如果可变数据的值能在其他地方计算出来,使用 「查询取代派生变量」来重构;

7. 发散式变化

如果某个模块经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了;

  • 如果增加一个新功能,需要对原有功能做出大量的修改。这就是散发是变化的征兆
  • 每当要对某个上下文做修改时,只需要理解这个上下文,而不必操心另一个,“每一次只关系一个上下文”这一点很重要;
  • 重构方法:
    • 如果发生变化的两个方向自然地形成先后次序,可以用 「拆分阶段」 将两者分开;

8. 霰弹式修改

如果修改的代码散步四处,不但很难找到他们,也很容易错过某个重要的修改;

  • 重构方法:
    • 应该使用 「搬移函数」 和 「搬移字段」 把所有需要修改的代码放进同一个模块里;
    • 面对霰弹式修改,一个常用的策略就是使用与内联相关的重构—如 「内联函数」 或者 「内联类」,把本不该分散的逻辑拽回一处;

9. 依恋情结

所谓模块化,就是力求将代码分出区域,最大化区域内部的交互,最小化跨区域的交互。

  • 有时会发现,一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所有的模块内部的交流,这就是典型的依恋情况;
  • 重构方法:
    • 这个函数想跟这些数据待在一起,就可以使用 「搬移函数」 把它移过去。
    • 有时候函数中只有一部分受这种依恋之苦,这时应该使用 「提炼函数」 把这部分提炼到独立的函数中,再使用 「搬移函数」 ;

判断哪个模块拥有的词函数使用的数据最多,然后就把这个函数和那些数据摆在一起;

10. 重复的 switch

任何 switch 语句都应该用 「以多态取代条件表达式」 消除掉,所有条件逻辑都应该用多态取代,绝大多熟 if 语句都应该被消除掉;

  • 在不同的地方反复使用同样的 switch 逻辑,重复的 switch 的问题在于:每当你想增加一个选择分支时,必须找到所有的 switch ,并逐一更新。多态可以帮我们写出更优雅的代码库;

11. 循环语句

函数作为一等公民已经得到了广泛的支持,因为可以使用 「以管道取代循环」 来让循环消失。

  • 「管道操作(filter/map)」 可以帮助我们更快地看被处理的元素以及处理它们的动作;

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦