书籍导读 《重构——改善既有代码的设计》

书籍导读 《重构——改善既有代码的设计》

任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员
–Martin Fowler

1. What ? 什么是重构?

重构,这是大家在日常工作中很常用的一个词,但严谨的来说,很多时候,我们的所说的重构不是重构,而是重写!

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构!

重构过程不能破坏甚至改变软件外在功能,如果进行一次重构重构前,这段代码有一个bug, 那么重构后这个bug应该还在!

如何进行“不改变软件可观察行为”的修改动作,比如:

删除无人使用的变量、函数或类。
添加无人使用的变量、函数或类。
将对一个变量或函数的引用,改为引用另一个与之完全相同的变量或函数。

从这样简单得近乎“废话”的基础开始,构建起了整个重构的理论体系

阅读第一章

2. Why? 为什么重构?

软件的设计不必甚至不可能是一开始就做好的,软件一定会不断修改。所以真正重要的不是如何提前做一个完美的设计,而是在不断修改的过程中,让软件的设计水平不变差,乃至变得更好。

在代码写好后改进它的设计,设计不是在一开始完成的,而是在整个开发过程中逐渐浮现。重构的每个步骤都很简单,甚至时过于简单,但这些小小的修改累积起来就可以根本改变设计质量!

  • 改进软件的设计

  • 提高代码质量,更易被理解

  • 尽早的发现bugs

  • 添加功能更快!修复bug更快!

项目的维护成本远高于开发成本

3. When ? 什么时候重构?

  • 在编程的每一分钟里,随时进行重构

  • 三次法则:事不过三,三则重构!

第一次做某事时,放手去做,第二次做类似的事会产生反感,但还是可以去做,第三次再做类似的事,你就应该重构!

见机行事:

  • 最佳时机:添加新功能前, 尤其是当你想要复制粘贴时

  • 修改BUG时,

  • 捡垃圾式重构,重构需要花费一定的精力时,至少让营地比你到达时更干净!

    重构不是与编程割裂的行为,你不用专门安排时间重构,在你做其他事时自然发生!

    Code Review 时:

    ​ * 每一次做代码评审的时候

    什么时候不应该重构:

  • 不需要修改时,比如丑陋的代码被隐藏在一个API下

    • 重写比重构更容易

4. Where? 重构哪里?

阅读第三章:代码的坏味道

  • 神秘命名
  • 重复代码
  • 过长函数
  • 过长参数列表
  • 全局数据
  • 可变数据
  • 发散式变化
  • 霰弹式修改
  • 依恋情节
  • 数据泥团
  • 基本类型偏执
  • 重复的switch
  • 循环语句
  • 冗赘的元素
  • 夸夸其谈的通用性
  • 临时字段
  • 过长的消息链
  • 中间人
  • 内幕交易
  • 过大的类
  • 异曲同工的类
  • 纯数据类
  • 被拒绝的遗赠
  • 注释

过长函数

1
没有什么是增加一层“间接层”不能解决的。如果有,那就增加两层
1
2
小函数会给代码的阅读者带来一定的负担,因为必须不断切换上下文,才能看明白函数在做什么
让小函数易于理解的关键在于良好的命名
1
当你感觉需要以**注释**说明点什么的时候 ,我们就需要把说明的东西写入一个独立函数中去 ,并以其用途(而非实现手法)命名 ,我们可以以一组甚至短短一行代码来做这件事 ,哪怕替换后的函数调用比函数自身还长,只要函数名可以解释其用途,就值得 ,关键不在与函数的长度,而是函数“做什么”与“如何做”之间的语义距离
1
99%的场合,把函数变短,使用**提炼函数**方法就足够了如果有大量参数和临时变量,可以使用**已查询代替临时变量**消除临时变量**引入参数对象** 和 **保持对象完整** 可以将过长的参数列表变的简洁... ...

5. Who ? 谁来重构?

代码属于集体

集体对代码负责

每个人都有义务消除坏味道

6. How ? 如果重构

阅读重构名录,《重构》时一本指导手册,参照这本手册,逐个。

第一组重构

提炼函数

内联函数

提炼变量

内联变量

改变函数声明

封装变量

变量改名

引入参数对象

函数组合成类

函数组合变换

拆分阶段

  • 封装

  • 搬移特性

  • 重新组装数据

  • 简化条件逻辑

  • 重构API

  • 处理继承关系

8. 其他

消除味道:一个重构应该是从识别一个坏味道(Bad Smell)开始,以消除一个坏味道结束,任何不以消除坏味道为目标的重构都是耍流氓。

始终工作:即重构定义中的“在不改变软件可观察行为的前提下”,说白了就是重构过程不能破坏甚至改变软件外在功能。

持续集成:不需要为重构单建分支,重构过程可以做到与开发分支在同一分支上持续集成持续交付。

随时中止:例如一个方法重命名,需要修改100个调用点,当改到50个的时候有个紧急的任务,我可以随时暂停重构,立即切换到Feature开发上,且不需要回滚已做的重构。

断点续传:还是上边的例子,假如我已经完成了紧急任务的开发,可以随时继续之前的重构,完成剩下50个调用点的重命名。

过程可逆:对于重构,经常有人会问:你怎么保证重构就会更好而不是更坏呢?重构的伟大就在于他跳出了对错之争,将关注点放到如何快速平滑安全的变化上,当然也包括反向重构。所以我的回答是:无法保证,但是我可以一分钟就重构回来。如果仔细看,《重构》书里的所有重构手法都是双向的,比如“提取函数”和“内联函数”。

好的重构应该像一边开车一边 换轮胎一样,保证系统随时可工作的前提下,还可以对其结构做出安全高效的调整。

重构是完全针对代码结构的行为,与代码的逻辑没有关系。

《重构》的第三章给出了22种代码的“坏味道”(在第二版里扩展成了24种),读者只需要在自己的代码中寻找这些不良的结构,然后遵循对应的重构手法一步步修改下去就行了,并不需要预先理解代码的逻辑——随着重构的进行,代码的逻辑自然会浮现出来。

迷思和误解

迷思和误解

  • 这周重构下项目

    重构是无数微小的、不改变软件行为的修改步骤组合,可以随时进行、随时停住,没有必要、也不应该集中做大块的重构。

  • 不小心把代码重构坏了

    重构的每个动作都应该是行为保持的(即:不改变软件可观察行为),严格说来,在重构过程中,软件应该每分钟都处于可工作的状态,根本不应该有“重构坏了”的状态出现。

  • 整理好需求,才好重构

    重构是完全针对代码结构的行为,只需要从坏味道入手、运用适当的重构手法,不需要预先了解代码的逻辑

没有银弹,重构技能需要刻意练习

只有通过刻意练习,掌握正确的重构操作方式,才能在各种复杂的真实情况中运用重构技巧,从而解开祖传代码的死结。

挑战:延缓新功能开发

代码所有权: 代码属于集体,集体对代码负责!

测试: 不该变程序可观察的行为,如何保证不出错?

注意:错误不可避免,自测试的代码可以快速发现错误并解决错误

重构与性能: 重构可能会使软件变慢,但它可以使软件的优化更容易!

自动化重构:

​ JetBrains 重构相关快捷键

提炼函数 Ctrl+alt+m

提炼变量 Ctrl+alt+v

生成测试 Ctrl+alt+t