读书笔记《重构 改善既有代码的设计》(第2版本)

  • 2021 年 1 月 14 日
  • 笔记

前沿,印象

  重构是什么,用来做什么,怎么做,what、why、how,而这一句话:便于理解,便于修改,是重构这个方法最直白的解释了。当然,书里面也包括日常遇到的每一个平平无奇的重构招式、方法背后,其实都有一些理论或者思想在支撑着,比如单一职责原则,以及其他几个原则(开闭原则、里氏替换原则、接口隔离原则、依赖倒置等),这些都是前辈们总结出来的,遵循原则,让写出来的代码能够高内聚、低耦合,软件项目也能够更加健壮,维持更好更长的生命周期。

  什么样的代码才是好的代码,其实这又回到编程语言本身了。编程语言的发展,从古早的机器语言(01),到后来的汇编语言(命令式),到高级语言,其实就是一个从复杂到简单的过程,编程语言是让人(程序员)来使用的,只有更多的人易于理解,才能更好的使用。如果论性能的话,自然是机器语言和汇编语言性能最好了,但是它们太难记忆了,也没人能看得懂,因此才出现了高级语言。最好的理解的,自然是人类语言,但是又过于灵活,过于没有规则,计算机又识别不了了,因此尽可能的接近人类语言,更易于理解的,就是更好的代码。用更高层次的抽象,来取代基本的控制结构,这样可以让开发者聚焦精力于业务场景而无需费心复杂底层运作。提升代码信噪比(简洁性),尽量减少不确定因素来使代码极度简洁。从这一点来说,伪代码反而是最好的代码,因为它不注重细节,注重业务,便于人们理解,“我不管你怎么实现,我就要这种效果”,几乎没有bug。

  好的代码和坏的代码,也许更多的区别是通过一些细节来体现的,比如方法或变量的命名(这本书里面把方法叫做函数了),比如一些很细微的习惯等等,这些小的综合起来却产生一些质的变化。类比一下就如同建设一栋大厦,质量好的建筑,从一砖一瓦都可以看出来。

  这本书语言很平实,没有纠结于讲道理,讲理论,或者是叙述代码历史,而是通过一些很家常的例子,结合手法,润物细无声的把重构的精髓给讲了出来,重点章节值得多次去品读,结合一些设计模式,会有更深入的理解。

  当然也有一些缺点,比如例子用js写的,某些特性是Js适用的,不好理解,有些翻译腔有点重,不够local,读起来略吃力,翻译不够信达雅。

第1章 重构,第一个实例

  通过一个例子,让人对重构有了个感性的认识,例子中用到了本书大部分的一些重构手法,比如提炼函数,内联变量,搬移函数,用多态代替条件表达式等,看完之后,对重构有了个基本的了解了,哇,原来这就是重构,我也可以行。里面用到的手法,可能自己平时就在用,某个方法或者经常用,或者不经常用,或者呢,偶尔也会用到这个手法。比如提炼函数,当被复制粘贴一段同样的重复的代码折磨的不行的时候(通常会需要多处修改那种),也会给这段代码封装一下,放在类的底部,用一个private修饰的方法,这样这个类其他地方也可以用啦。再进一步呢,跨类跨模块也用到了,怎么办,那就放在logic里,成为一个public的方法咯,这样其他模块也能用到啦。自我总结一下,是被动的去使用重构,或者说是不规律的去使用,没有一个通用的或者是主动的去推动去使用。

  还有一个印象比较深的地方,就是小步快跑式的去重构,去修改,去迭代。第一章的这个例子,有时候看感觉好繁琐啊,每次都动那么一行代码,就像人说话很啰嗦的样子,然而,这恰恰是这本书或者说是作者的良苦用心所在,这也是重构的精髓了,一方面重构一定要像搭积木从地基做起一样,没有副作用的去重构,小步快跑式的去做,要经常的运用重构,命名不合理,代码让人理解费力,那么就是重构的时机了。另一方面,每次都挪动一行代码的方法,其实最重要的还是在于理顺思路,理解代码的逻辑,这也是重构的一个本质了。

  记得刚入职时的一个培训,第一步就是了解需求,然后分解任务,设定验收标准等,其中一句话记得挺久,编程人员千万不要分配一个任务,立马就去写代码,而是要去分解,去理顺思路,然后建工作项,分解任务,然后才是快乐的code。

第2章 重构的原则

  第一章已经对重构有了个基本的认知了,什么是重构,就是那一点一点的移动啊、修改啊,平平无奇又看似无效率,看似简单,其实背后有很多的理论参与进来。重构并不是有代码洁癖才去做,而是有真切的好处。就像一个人,平时一定要保持一些好的习惯(比如阅读、健身、规划、跑步等),这些好的习惯,会不知不觉影响你,让你成功(听起来很毒鸡汤呢)。那么在编程领域呢,一定要保持一些好的习惯、原则、思维、乃至思想,这些会影响你,让你成为一个好的coder,重构或者说重构的这些手法,就是区分好的码农和不好的码农的所在了。

  何谓重构,两顶帽子,重构既是名词又是动词,比如说把某个模块重构一下,用到了哪哪些重构手法。往大的讲,重构可以作为编程人员的一个思维了,结合第三章的代码的坏味道,当你去做一个方法,一个模块的时候,就要带着重构的思维去code,预见有哪些地方可能要重构等等,规避哪些坏味道。

  为何重构,没有银弹,想起《人月神话》,重构本身不是一蹴而就,也不是单个手法、技术,而是一系列组合,并且重构也可能在某个时间觉得这样的重构好,在另外一个时间,可能觉得另一种更好,比如函数提炼,内联变量等,可能相互转换。重构的好处显而易见,你好我也好,假如是一个人的项目,你自己可以更好的理解(常常我们自己写的代码,几个月或者几天后,就会迷惑,需要重新厘清思路),假如是团队项目,别人更容易理解了。

  何时重构,老话讲的好,有再一再二没有再三,我觉得这句话对于我来讲过于真实了,每次自己做的重构,都是觉得不能再重复复制代码、多处修改代码,长痛不如短痛,然后提炼、封装。预备性重构,大约讲的是将重构变成一种习惯,每次写代码都要带着这个念头、思维。

  怎样对经理说,这也是一个矛盾点,大约是和唯效率论冲突,毕竟重构是在旧的代码上做一些加减法,短期内基本没有产出,还不如修几个Bug来的实在,没有价值或者价值常常被忽略。其实重构常常会消灭一些潜在的bug,当然,最重要的好处,就在于软件项目是一个工程,重构就是一个质量监督员,能够使得质量更为良好,更为健壮。

第3章 代码的坏味道

  一个项目会腐烂变质,还是因为代码有坏味道,一点一滴积累起来,导致项目不堪负重,不得不推倒重来。代码的坏味道,大概就是程序员自我调侃的“祖传代码”类似,祖传代码不能动,轻则伤筋动骨,重则一命呜呼。只能自己看懂,别人看不懂也不敢改。关于注释,本书讲的是,好的代码不应该有注释,因为粒度够细,够抽象,代码自己就有解释性,通过命名、上下文即可看出意图、组什么等,之前看另一本书,说的是好的项目应该有注释或者文档,恨不得每一行代码都要有注释,又是走向另一个极端了,见仁见智。

  代码的坏味道这一章,从常见的那些不合理命名,到不合理的方法、逻辑等,只要让人感觉到难以理解,其实都是代码不合理导致的。好的代码,应该是接近人类语言或者说人的思维,让人能够很顺畅的读懂、去理解代码是去做什么的,怎么做的。可读性在某种意义上,是好代码和坏代码的区别。因为可读性有时候是衡量代码有多好的很重要指标之一,可读性最终决定了代码的可维护性,即使代码的唯一读者是我们自己本人。我们常常试着去读好久之前自己写的代码,都有可能被绕晕,或者耗费大量的时间去复现去理清思路,当时基于什么考虑等等,特例用来干什么,更不用说让其他人来读、来修改我们的代码了。出现问题时,当然是更简介、更易读的代码,更好去排查问题。

  看这一章时,确实能看的很惭愧,因为很多不合理的代码,自己经常在开发中出现,比如过长的函数、过大的类、过于复杂的循环、if…else等嵌套等,曾经做过一个方法,因为要判断各类状态,编写完大约有五百行的代码,每当出现bug调试时,总是很崩溃,无从下手。代码的坏味道,根本原因还是在于开发这个功能时没有想好,没有规划好业务逻辑,然后就急匆匆的下手编码,导致枝杈横生,逻辑复杂,不得不采用很多if…else判断、嵌套、循环等来弥补。因此写代码前一定要先思考,先分析逻辑,整理业务,必要时画图、流程图等,总结之后,觉得逻辑没有问题,才是下一步的写代码。

第6章 第一组重构

  从本章开始,就是最常用的、也是比较精髓的一些重构手法了,刚开始看时是觉得很平平无奇的,很多就是那种挪一行代码,再挪一行代码,有些手法还是有对应的反过来的手法,比如内联函数——提炼函数,走一步退一步等于没走那种。难得之处就在于作者能够手把手的一步一步的引导,不厌其烦的示范。这一章值得多次重复的去阅读去理解,做笔记做小抄,并且要记下来养成习惯,平时主动去做。很多重构手法看起来很简单,但是背后的思想或者说更深的理论,却值得思考。所有的重构手法,都是服务于“易于理解,易于修改”这一点。重构是一步步向着粒度更小的代码前进,符合了单一职责原则。

  提炼函数,重构的第一步,避免过长的函数的克星,首先分析业务逻辑,然后分拆不同的业务模块,分清做什么,平时写代码要养成写非常小的函数的好习惯,通常只有几行,超过六行就散发臭味。

  反向重构,内联函数,如何掌握与提炼函数之间的度,不能为了重构而重构,过度重构,不然就变成一堆调用链了。

  提炼变量,将某些值提炼为有意义的变量,这样一个好处是能够调试(自己经常用到,用过之后再删掉这个变量),另一个好处就是区分状态值,明白这个逻辑是干什么用的,尤其是在if等的条件表达式中很有用,一堆的条件与或,通常让人看得头大,提炼变量就等于提炼逻辑出来了。

  反向重构,内联变量,这里要区分有用的变量和多余的变量之间的关系,假如本身状态值不多,或者逻辑不复杂,很容易就能看懂,没有经过复杂的计算、与或合并等,那么就不需要额外的变量了,额外的变量也是导致过长函数的一个原因吧。

  改变函数声明,其实就是函数改名,类似的有变量改名,一个有意义的、逻辑清晰的命名,非常重要。相信每个人看到 var aaa = something肯定是崩溃的,混淆式命名大概属于祖传代码的一部分吧。一个好的命名是基础,怎么做、干什么的,能够体现出来业务逻辑来,让人明白无误,无歧义。要用心命名,可能要经过多次修改。

  函数组合成类、函数组合成变化,讲的都是将相似的、同类的业务逻辑合并,其实就是面向对象的封装。

  拆分阶段,和函数组合成类相反,本质还是理顺业务逻辑,将不同的逻辑拆分出来,独立成一个函数。

第7章 封装

  封装类、封装函数、封装记录(数据)、封装字段,上一章是小打小闹,一行一行搬移语句的话,这一章开始进行大的重构了,封装、继承、多态,是面向对象语言的三个基本特征,本书里面一些大的重构,也基本贯穿的就是这三个特征了。其实封装哪些类、封装哪些函数,最重要的,就是拆分逻辑,将相似逻辑、相似功能的代码块放在一起,进而隐藏细节的实现逻辑(比如一个循环、一个判断等),将实现提升到高阶,使用者无需关心内部实现,只需要使用即可。

  这一章完全就考验的就是规划能力了,哪些需要封装,该如何对外暴露等等,多用多重构,熟练了就好。多去重构个几次,就明白怎么拆分业务逻辑,怎么去拆分大的类、方法,然后封装成小的类、精准的类。

看完这么多封装的手法,比如封装记录,封装类、封装函数,总结就是思想很重要,手法不重要,你是如何提炼功能,比你敲更快的代码更重要。

第8章 搬移特性

  这一章开始挪语句挪方法挪代码的乾坤大挪移了,如何挪,一句一句的挪,有什么用,将功能相关的代码放在一起,整理在一起,在挪的过程,就是逻辑的整理过程,相同引用上下文的代码放在一起,下一步就是提炼逻辑、封装函数、封装类、封装记录。

  以管道取代循环,管道大概是js的一个特性,在c#中,大概就是lambda或者linq,这么做有什么好处,大概就是函数式编程的好处了吧,以前写过关于List的ForEach方法的使用,集合List的ForEach用法:用函数式编程替代面向过程,好处就是更简洁,更高阶,关于函数式编程,以前也总结过,产品开发中的函数式编程思想,有现成的、更好的当然就用现成的了。

第9章 重组数据结构

  重组数据接口这一章基本就是围绕数据结构比如字段、记录、变量等做重构,值对象改为引用对象、引用对象改为值对象,感觉是js里才有的,因为有作用域、闭包之类的。其他的变量重命名等,前几章也讲过,感觉这一章没那么重要。

第10章 简化条件逻辑

  这一章和第6章、第7章、第8章,感觉是需要时不时的重看的章节,温故而知新,每次看应该都有收获,所有的重构手法都需要牢记在心,熟练运用。这一章对我而言也很震撼,在开发中犯的一些错误,也能一一找到解决方法,这才是最实用最能解决问题的钥匙。比如if…else导致的过长的函数、过大的类、switch等,都获益匪浅。卫语句与单一出口入口之争,解决了嵌套过多的问题。总之这一章对实际工作中很有指导意义,解决了一些燃眉之急,相比封装、移动大法,简化条件逻辑是看得见的好处,起码能解决短痛。

当然,简化条件逻辑,本质还是在于封装重构,将不同的分支独立成一个函数,执行不同的逻辑业务,这一点才是简化的重点。

第11章 重构API

  待补充

第12章 处理继承关系

  待补充