重构读书笔记一

重构 Dec 17, 2019

译者前言

重构的定义:重构是在不改变软件可观察行为的前提下改善其内部结构。本书的定义的重构是在不了解软件行为的基础上进行的重构。如果软件可以通过代码了解他的行为,那么他就还没坏到一定的程度。

作者前言

什么是重构

在不改变代码外在行为的前提下,对代码作出修改,以改进程序的内部结构。本质上说,重构就是在代码写好以后改进他的设计

本书的作用

本书是一本重构指南。
目的:如何以一种可控制且高效率的方式进行重构。

章节目录

第一章通过一个小程序展示重构的过程和手法
第二章讨论重构的一般性原则、定义、以及进行重构的原因,以及重构所存在的一些问题。
第三章介绍如何嗅出代码中的坏味道,以及如何运用重构清除这些坏味道。
第四章介绍如何运用一个简单而且开源的 Java 测试框架,在代码中构筑测试环境。
第五章到第十二章介绍本书的所有重构手法。
第十三章讲述将重构技术应用玉商业开发中的一些问题
第十四章展望重构技术的未来——自动化工具

第一章

本章通过一个程序像我们展示重构的过程和方法

重构的第一步

为即将修改的代码建立一组可靠的测试环境,这样我们可以小步修改,每次修改后都测试

154 拆分阶段

重构的拆分阶段,主要是为原函数添加足够的结构,以便能更好的理解它。把复杂的代码分解为更小的单元,以及用更好的命名便于理解。
ps:拆分后,代码的逻辑会变得清晰,各个模块之间的关系是显而易见。整个流程也很清晰,这时候可复用的模块就像清澈小溪的玛瑙石一样可见。
这样想拆分阶段,就像是清理浑浊的溪水。首先要清除杂物,然后让溪水流动,沉淀下来。
ps:作者也认为可变的状态很快就会变成烫手山芋。状态的变换应该在一个函数范围內。如果修改了状态应该作为一个用新值传递给另外的函数。

106 提炼函数

将相同的代码提炼成为函数
ps:傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码的,才是优秀的程序猿。

123 內联变量

对于函数的返回值如果没有修改没有必要声明一个变量,可以将变量內联。

124 改变函数声明

移除参数,如果一个参数可以被內联获取,就不需要在声明处传入了。我们需要首先将参数替换掉,然后改变函数声明。

ps:作者会在动态语言中将变量的类型写在结尾,例如:userList

198 搬移函数

将公用的业务代码搬到父类里面,将公用的逻辑代码搬到工具类里面。

227 拆分循环

将循环的过程变成一个函数,然后可以用內联变量。
ps:不断运用上面的方法重构后,顶层函数应该只有和业务相关的逻辑,也就是一个流程图里面最外层的抽象。计算,存储,缓冲,重试等代码不应该在顶层代码存在。

231 用管道取代循环

管道就是java里面的stream,spark里面的算子等
ps:我也喜欢这样使用,因为很方便而且直观,语义化。

功能重构阶段

接下来我们着眼于扩展的功能:支持更多的类型。
ps:代码世界里面不变的对象是抽象的事物,变化的是具体的事物。比如:计算。我们可以做钱的计算,货物的计算,时间的计算。数学上,整数的计算,小数的计算,字符串的计算,复数的计算等等。计算的抽象是可以应用到很多事物上,但是具体的实现是不断变化的。找出业务上不变的抽象点,他们之间的关系。动作的最后变成了流程图。抽象成事物,变成模块,最后汇聚成架构图。

272 多态取代条件表达式

将条件表达式的判断和对应业务处理逻辑抽取到一个类里面。这样if elif else的结构就可以用多态取代。

362 子类取代类型码

用多个子类来取代类型码。

334 使用工厂函数取代构造函数

多态结构唯一需要多条件判断的地方,但是这里的判断条件也应该尽可能的简单,否则抽象成多态就没有意义了。

ps:很多时候,我们觉得使用工厂方法和使用switch或者if的判断逻辑上并没有太多的区别。那么区别在哪?我以为是关注点分离。人类能关注和计算的东西着实有限。把相关的代码写在一起,才是人能读懂的代码(低耦合,高内聚。不要和陌生人说话,模块之间应该最小数据交流)。但是你说这样的话,我把代码都写在一起不就完事了嘛。这也不行,为什么,记不住。给你一本书10页从头读到尾,你能记住多少?但是如果每页有一个主题,那一页的内容都是讲这个主题的,等你读到后面的时候,至少能记住前面的几个主题。这样这本书的内容你就大致了解了。但是如果我讲一个主题,用了1.5页,再讲一个主题用了2.5页。这你能准确找到每个主题的位置吗?那我再改改,前面1.5页,讲三个主题。然后再用2页讲三个主题,最后再用4.5页把前面的三个主题和剩下的三个主题一起讲,最后再用2页讲一个主题。你能捋清楚到底讲了什么吗?
所以代码不能写太长,人记不住。相同业务的代码要聚合在一起。状态不要总变(函数式编程)。一个函数最好只干一件事情,函数名字一定要和准确描述干什么的(unix哲学,kiss原则)。业务函数和类只处理业务,就像流程图的映射。其他的事情全部外包出去(依赖倒置)。设计多态的时候,如果子类不能替代父类,设计的一定有问题(里氏原则)。
最后的结果是什么? 对修改封闭,对扩展开放。(开闭原则)

结语

作者在第一章向我们展示了多种重构的手法。
本章有三个要点:

  1. 将原函数分解为一组嵌套的函数
  2. 应用拆分阶段分离计算逻辑与输出格式化逻辑
  3. 为业务引入多态性来处理计算逻辑
    作者每一步都给代码添加了更多的结构,以便能更好地表达代码的意图。
一般来说,重构早期的主要动力是尝试理解代码如何工作。通常你需要先通读代码,找到一些感觉,然后再通过重构将这些感觉从脑海里搬回到代码中。清晰的代码更容易理解,使你能够发现更深层次的设计问题,从而形成积极正向的反馈环。当然,这个示例仍有值得改进的地方,但现在测试仍能全部通过,代码相比初见时已经有了巨大的改善,所以我已经可以满足了。

我谈论的是如何改善代码,但什么样的代码才算好代码,程序员们有很多争论。我偏爱小的、命名良好的函数,也知道有些人反对这个观点。如果我们说这只关乎美学,只是各花入各眼,没有好坏高低之分,那除了诉诸个人品味,就没有任何客观事实依据了。但我坚信,这不仅关乎个人品味,而且是有客观标准的。我认为,好代码的检验标准就是人们是否能轻而易举地修改它。好代码应该直截了当:有人需要修改代码时,他们应能轻易找到修改点,应该能快速做出更改,而不易引入其他错误。一个健康的代码库能够最大限度地提升我们的生产力,支持我们更快、更低成本地为用户添加新特性。为了保持代码库的健康,就需要时刻留意现状与理想之间的差距,然后通过重构不断接近这个理想。

ps:小而美,命名符合人类的直觉是我希望达到的标准

作者说:好代码的检验逻辑就是人们是否能轻易地修改它。
最后作者告诉我们最重要的一点就是节奏感,以前我做重构的时候,总想着把一坨代码快速理清,重构好。但是作者向我展示了小步迭代重构的神奇力量。所以面对糟糕的项目的时候,只有整体重构一种方法了吗?由此我们可以在每次迭代需求的时候在小范围內进行重构,只要你不引入新的混乱度,并且不断减少以前的混乱度,那么代码终究会变得整洁。

这个示例告诉我们最重要的一点就是重构的节奏感。无论何时,当我向人们展示我如何重构时,无人不讶异于我的步子之小,并且每一步都保证代码处于编译通过和测试通过的可工作状态。20年前,当Kent Beck在底特律的一家宾馆里向我展示同样的手法时,我也报以同样的震撼。开展高效有序的重构,关键的心得是:小的步子可以更快前进,请保持代码永远处于可工作状态,小步修改累积起来也能大大改善系统的设计。这几点请君牢记,其余的我已无需多言。