《重构》---- 读书笔记
读《重构》(第二版)有感
开篇一些好的语录
- 软件不死,重构不歇。----—余晟,《代码整洁之道:程序员的职业素养》译者
- 重构的最终目的始终是为了减少重构。
- 重构的先决条件是:测试先行。
第一章手札
- 重构手法

1 | class Movie { |
以上就是多态的展现手法之一,重写。
当然。还可以通过方法重载去实现,不过js
通常需要借助arguements
去实现,利用typescript
去强化实现则更佳。
个人观点:我觉的重构是什么?当读完第一章后,我觉的跟我之前理解的重构相差不大。
我们在每一次迭代中,尽量不去动那些运转正常的代码,当迭代或者需求变更时,那些“不该动”的代码,不得不动的时候,这个时候就需要警惕,我们需要重构了。
然而,重构需要遵循以下原则:
- 不破坏以往的需求点
- 重构之前有完整的测试用例支撑现有功能点正常运行,保证我们写的重构代码是“可以被信任的”,可以用于“生产环境的”。
- 重构的目的是更好的维护与添加现有功能,而不是仅仅是当前迭代的重构,重构应该本着不需要再次重构的方向去靠拢。
第二章手札
带着下面的疑问,我们继续探索书籍的第二章:
- 什么时候应该重构,什么时候不应该重构?
- 重构势必会增加工作量,我们应该如何应对?
- 好的代码需要被重构吗?
好的设计方法:
- 简单设计
- 增量式设计
- YAGNI[mf-yagni] ——“你不会需要它”(you arenʼt going to need it)的缩写
重构与性能
克莱斯勒综合薪资系统的支付过程太慢了。虽然我们的开发还没结束,这个问题却已经开始困扰
我们,因为它已经拖累了测试速度。Kent Beck、Martin Fowler和我决定解决这个问题。等待大伙儿会合的时间里,凭着对这个系统的
全盘了解,我开始推测:到底是什么让系统变慢了?我想到数种可能,然后和伙伴们谈了几种可能的
修改方案。最后,我们就“如何让这个系统运行更快”,提出了一些真正的好点子。
然后,我们拿Kent的工具度量了系统性能。我一开始所想的可能性竟然全都不是问题肇因。我们
发现:系统把一半时间用来创建“日期”实例(instance)。更有趣的是,所有这些实例都有相同的几个
值。于是我们观察日期对象的创建逻辑,发现有机会将它优化。这些日期对象在创建时都经过了一个
字符串转换过程,然而这里并没有任何外部数据输入。之所以使用字符串转换方式,完全只是因为代
码写起来简单。好,也许我们可以优化它。然后,我们观察这些日期对象是如何被使用的。我们发现,很多日期对象都被用来产生“日期区
间”实例——由一个起始日期和一个结束日期组成的对象。仔细追踪下去,我们发现绝大多数日期区间
是空的!处理日期区间时我们遵循这样一个规则:如果结束日期在起始日期之前,这个日期区间就该是空
的。这是一条很好的规则,完全符合这个类的需要。采用此规则后不久,我们意识到,创建一个“起始
日期在结束日期之后”的日期区间,仍然不算是清晰的代码,于是我们把这个行为提炼成一个工厂函
数,由它专门创建“空的日期区间”。我们做了上述修改,使代码更加清晰,也意外得到了一个惊喜:可以创建一个固定不变的“空日期
区间”对象,并让上述调整后的工厂函数始终返回该对象,而不再每次都创建新对象。这一修改把系统
速度提升了几乎一倍,足以让测试速度达到可接受的程度。这只花了我们大约五分钟。
我和团队成员(Kent和Martin谢绝参加)认真推测过:我们了若指掌的这个程序中可能有什么错
误?我们甚至凭空做了些改进设计,却没有先对系统的真实情况进行度量。我们完全错了。除了一场很有趣的交谈,我们什么好事都没做。
教训是:哪怕你完全了解系统,也请实际度量它的性能,不要臆测。臆测会让你学到一些东西,
但十有八九你是错的。——Ron Jeffries
上面的故事,其实也从反面思维告诉了我们一个道理,写好每一行代码是要义,还记得以前看过一句话一个优秀的程序员并不是满天飞的架构,而是他写的每一行代码。
看完了一整章的内容,有必要回答刚开始的三个问题:
- 什么时候应该重构,什么时候不应该重构?
- 当一大堆“丑陋”的代码工作正常,但是不影响你正常开发下,我们并不需要重构它,因为可能会显式的增加你的工作量。那么,什么时候应该重构呢?当你的需求要求你不得不改动
原有的“丑陋的代码”的时候,这个时候你就需要考虑重构了。
- 当一大堆“丑陋”的代码工作正常,但是不影响你正常开发下,我们并不需要重构它,因为可能会显式的增加你的工作量。那么,什么时候应该重构呢?当你的需求要求你不得不改动
- 重构势必会增加工作量,我们应该如何应对?
- 提前做好预算是必须的,特别是时间预算,务必准备好你的“健全”的测试用例。
- 好的代码需要被重构吗?
- 答案是很显著的,好的代码的也需要被重构,随着每次迭代的进行,好的代码终将会有“不好”的那一面,始终记得我们一开始说过的“软件不死,重构不歇”。
第三章 手札
无论是重构还是设计代码,我们的起点始终是写好每一行代码。
好的代码的基本素养:
- 好的命名:函数声明(124)(用于给函数改名)、变
量改名(137)、字段改名(244)等,其实有经验的程序员都明白一个东西就是,好的命名是不需要加注释的。 - 避免重复代码:简洁代码的要义的“抽象”,抽象一切可复用逻辑;
- 过长函数的优化:短小精悍的函数,更容易阐释其意思。(这里的过长函数指的是函数体内容过长,而不是函数命名);
- 过长参数列表的优化:对象序列化参数;
- 抽离全局变量,添加作用域,避免污染全局;
- 注释,一些注释是必须的,但是在写注释之前,请先考虑重构是否可以解决添加注释的必要;
当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。
小结:其实这一章节的内容主要还是阐述了如何去写好每一行代码,如何约束程序员去书写健壮的代码;’
第四章 手札
这一章节,其实重点的笔墨应该在如何准备“健全”的测试用例上。
编写未臻完善的测试并经常运行,好过对完美测试的无尽等待。
不要因为测试无法捕捉所有的bug就不写测试,因为测试的确可以捕
捉到大多数bug。
每当你收到bug报告,请先写一个单元测试来暴露这个bug。
其实,我个人属于敏捷开发爱好者
,也就是TDD
驱动者,不过常常因为工时
制约了TDD
的路途,
因为TDD
的要义是:测试先行,由红变绿
;
至于应该进行什么测试黑盒测试
或者白盒测试
,取决于你的场景。
适当的补全一些BDD
行为测试的用例,方便产品
、测试
或者其他人员,像读短文一样读你的测试用例,在读懂测试用例的前提下,理解既有需求。
其实我觉得白盒测试更加适用于TDD
敏捷开发思维,适用于单元测试
层面。而黑盒测试
更加适用于BDD
思维,以行为驱动测试,以行为阐释需求。
End:关于解释“黑匣子”和“白匣子”,我觉得这里有一篇qs问答说的比较准确,也值得推敲。https://qastack.cn/software/27491/black-box-or-white-box-testing-which-do-you-do-first
第五章 手札
重构手法之核心要义
:
- 首先是名称(name)。要建造一个重构词汇表,名称是很重要的。
- 名称之后是一个简单的速写(sketch)。这部分可以帮助你更快找到你所需要
的重构手法。 - 动机(motivation)为你介绍“为什么需要做这个重构”和“什么情况下不该做这
个重构”。 - 做法(mechanics)简明扼要地一步一步介绍如何进行此重构。
- 范例(examples)以一个十分简单的例子说明此重构手法如何运作。
貌似第五章仅仅是作者的介绍重构的前戏
,欲听后戏
如何,请见下章内容。
第六章 重构名录
低层级代码重构的精髓—形成函数并给函数命名。
提炼函数法则
核心观点阐释了:应该将“意图与实现分开”,“函数不要过长”;
做法:
- 创造一个新函数,根据这个函数的意图来对它命名(以它“做什么”来命名,而
不是以它“怎样做”命名) - 将待提炼的代码从源函数复制到新建的目标函数中。
- 仔细检查提炼出的代码,看看其中是否引用了作用域限于源函数、在提炼出的
新函数中访问不到的变量。若是,以参数的形式将它们传递给新函数。 - 所有变量都处理完之后,编译。
- 在源函数中,将被提炼代码段替换为对目标函数的调用。
- 测试。
- 查看其他代码是否有与被提炼的代码段相同或相似之处。如果有,考虑使用以
函数调用取代内联代码(222)令其调用提炼出的新函数。
内联函数
核心观点:属于函数内部的代码就尽量去内敛它。
做法:
- 检查函数,确定它不具多态性。
- 找出这个函数的所有调用点。
- 将这个函数的所有调用点都替换为函数本体。
- 每次替换之后,执行测试。
- 删除该函数的定义。
提炼变量
提炼变量的法则其实很简单,就是将返回变量作为行内表达式,避免多声明一堆不必要的变量。
- 检查确认变量赋值语句的右侧表达式没有副作用。
- 如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试。
这是为了确保该变量只被赋值一次。
- 找到第一处使用该变量的地方,将其替换为直接使用赋值语句的右侧表达式。
测试。 - 重复前面两步,逐一替换其他所有使用该变量的地方。
- 删除该变量的声明点和赋值语句。
- 测试。
摘录来自: 马丁·福勒(Martin Fowler). “重构:改善既有代码的设计(第2版)。” Apple Books.
改变函数声明
通常的手法就是给函数换一个名字,或者去除不必要的函数,以这个函数做了
什么去描述,而不是如何去做
命名。
封装变量
动机:数据不可变性
;
做法:提供 get
| set
方法给使用者使用。
变量改名
好的变量是代码阅读的基石;
函数组合成类
当一组函数形影不离的操作一段数据,那么就可以利用函数组合成类;
代码拆分
将耦合代码拆分;
第七章 封装
封装是面向对象的编程思想,好的封装手法可以更好的杜绝一些不必要的失误,也是扩展性的阐释。