读《重构》笔记

以项目重构为契机,我读了《Refactoring Improving the Design of Existing Code》(Martin Fowler 著)的中文译本《重构 改善既有代码的设计》(熊节 译)。先附上中、英文电子版下载地址: link.

如果能力允许,读原著是首选。我主要读译本,看不懂的地方再看原著,帮助理解。

书中重构技术的范例介绍主要依托面向对象编程技术Java。英文版因为创作较早,用Java 1.1、1.2举例。而Java 1.1、1.2在2000年之前出版,如今Java已经升级到1.9了,可见书中有些技术已经过时。另外,如今的IDE具有强大的自动化重构功能,比如eclipse、Intellij Idea,书中对于重构的“危险”不用过于担心。本书更重要的是作者推崇的重构理念。

说明:个人认为书写的太啰嗦,以下引用部分做了提炼,并不是原句。

what

重构(refactoring):在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。
名词定义:对软件内部结构的调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。


此书适用范围:单进程程序。

单进程重构与并发和分布式程序设计的重构是完全不同的。
单进程的方法调用成本很低,在分布式软件中,方法的往返必须减至最低限度。


why

短期来看,对错误的修改更容易,不需要维护冗余代码;好的代码结构对追踪问题有帮助。
长期来看,重构可以使你更好理解代码的作用和运作方式,使得添加新功能更容易;有利于复用、扩展。


when

何时重构
不用专门拨出时间重构,不是为了重构而重构。重构必定是为了达到某个目的——因为你想做某件事,重构帮你把这件事做好。

如果你发现要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构,使特性添加比较容易,再添加特性。
事不过三,三则重构。(类似的事不要重复做)
想理解代码所做的事
修补错误
复审代码:某个团队进行设计复审,和单个复审者进行代码复审。

何时不该重构

现有代码不能正常运行。(应该“重写”而不是重构)
项目已近最后期限。

如果最后没有时间重构了,意味着你早该重构的。


how

以下排序尽为本文组织序列,与原著相差很大。

尽量除去临时变量(传来传去,容易跟丢,用方法代替,方便修改)
让每个方法返回一个值,安排多个方法返回多个值。

但是对于计算复杂度较高的方法,还是建议用临时变量吧。

最好不要在另一个对象的属性基础上运用switch语句。

在对象自己的数据上使用switch语句,或使用多态。

两顶帽子:添加新功能、重构

帽子代指行为:添加新功能,不可修改既有代码;重构,不能添加新功能。
在开发时切换两个角色。

改进设计的一个重要方向是消除重复代码

4.1 如果所有catch区段之内,都重复执行了同一段代码,就将重复代码移到finally区段。

注:书中为final,此处原著译文都有问题。

4.2 当子类中有重复的行为,应将该方法移至超类。
4.3 两个类有相同的方法,将方法子集提炼到一个独立的接口。

找出缺乏“间接层利益之处”,在不修改现有行为的前提下,为它加入一个间接层。
找出不值得的间接层,将它拿掉。

中间层可以提高复用率,也要去掉没有复用价值的中间层。

  1. 技巧

修改某个方法名称时,留下旧方法,让它调用新方法。

不要过早发布接口。修改你的代码所有权政策,使重构更顺畅。

“所有权政策”指的是“我负责的代码不允许其他人修改”。如果没有这么强烈的代码主人意识,合作伙伴重构了他的代码,他就可以修改调用代码。

为整个包定义一个异常基类,再定义异常子类,不影响调用者。

调用者只关心异常基类就好了

创建固定不变的对象。不再每次都创建新对象。
系统把大半时间都耗费再一小半代码身上。

如果创建对象消耗很大的资源,那就创建一个固定不变的对象,可以提升系统速度。

  1. 提炼代码的信号

寻找注释,条件表达式和循环。
当你感觉需要撰写注释时,先尝试重构,试着让注释变得多余。

Replace Inheritance with Delegation(352) 委托模式:用聚合来替代继承

  1. 将只赋值一次,不希望改变的变量声明为final

将临时变量声明为final,确保该临时变量只被赋值一次。
在较长的方法中使用final,帮助检查参数是否做了修改。
在对象创建之后不希望改变的字段,不要提供设值方法,声明为final。

间接访问变量的好处是,子类可以通过复写一个方法而改变获取数据的途径,支持更灵活的数据管理方式。

决定在接到请求时创建新的对象,还是预先将它们创建好。
用注册表对象(Dictionary)保存创建好的对象,比如运用Hashtable, HashMap, ConcurentHashMap...
equals()与hashCode()重写必须同时进行。

  1. 为什么最好不要把数据声明为public?

数据和使用数据的行为集中在一起,修改比较简单。

想想要修改散落在整个程序中的数据...Ouch~

若子类中只有常量方法,就没有存在价值。
在超类中设计与常量方法返回值相应的字段,去除子类,避免因继承而带来的额外复杂性。

保持代码清晰才是最关键的,“单一出口”规则其实不是那么有用。
用卫语句处理罕见情况,及时退出。
if-else结构每个分支是同等重要的。

  1. 引入Null Object的时机

只有当大多数客户端代码都要求对空对象做出相同响应时,声明Null Object才有意义。

  1. 令方法携带参数,以提高灵活性

一些做类似工作的方法,因少数几个数值致使行为不同,此时将这些方法统一起来,通过参数处理变化情况,使问题简化。

  1. 想到可能的情况,避免抛异常。

"异常"只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为,而不应该成为条件检查的替代品。

模板方法:将执行操作的序列移至超类,借助多态保证各操作仍得以保持差异性。

超类中创建这个方法,调用子类重写的方法。


关于测试

  1. 重构之前,首先要有可靠的测试机制,测试必须有自我检查能力。
  1. 撰写测试代码的最有用时机是在开始编程之前。
    明确功能需要做什么,接口设计,考虑意外情况的处理。
  1. 测试的目的是希望找出现在或未来可能出现的错误。
  2. 测试最担心出错的部分。
  3. 考虑可能出错的边界条件,把测试火力集中在那儿。
  1. 当事情被认为应该会出错时,别忘了检查是否抛出了预期的异常。
  1. “花合理时间抓出大多数bug”,好过“穷尽一生抓出所有bug”。
  2. 每当收到bug报告,先写一个单元测试来暴露bug。
  3. 随着测试类愈来愈多,可以生成另一类,专门用来包含由其他测试类所组成的测试套件。(这个类即“主控”测试类)

结语

书中的良言

没有任何量度规矩比得上一个见识广博者的直觉。
培养自己的判断力,一个类内有多少实例变量算是太大、一个方法内有多少行代码才算太长。
"得道"的标志是:你可以自信地停止重构。

真正去做才能有更多体会。
重构不是一劳永逸的,此时你需要这样重构,彼时你需要再用相反的方法改回去。就像书中列出了很多相反的重构技术,它们不是矛盾的,而是要看情况具体情况采用合适的方法。


附: 书中列出的相反重构技术

6.1 Extract Method 6.2 Inline Method
7.3 Extract Class 7.4 Inline Class
8.3 Change Value to Reference 8.4 Change Reference to Value
8.7 Change Unidirectional Association to Bidirectional 8.8 Change Bidirectional Association to Unidirectional
9.1 Decompose Conditional 9.2 Consolidate Conditional Expression
10.2 Add Parameter 10.3 Remove Parameter
11.1 Pull Up Field 11.5 Push Down Field
11.2 Pull Up Method 11.4 Push Down Method
11.11 Replace Inheritance with Delegation 11.12 Replace Delegation with Inheritance
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350