修改代码的艺术.pdf

修改代码的艺术.pdf
 

书籍描述

内容简介
理解修改软件的机制:添加特性、修正缺陷、改进设计、优化性能
把遗留代码放到测试用具之中
编写测试,防止引入新的问题
包含Java、C++、C和C#的示例,其中介绍的大多数技术适用于其他任何语言或平台
精确地确定要在哪些地方修改代码
处理非面向对象的遗留代码
处理看起来没有任何结构的应用程序

编辑推荐
世界级计算机专家Michael C. Feathers的经典之作,软件开发大师Robert C. Martin作序倾情推荐,修改遗留代码的权威指南
深入剖析修改遗留代码的各种方法和策略,从理解遗留代码、为其编码测试、重构及增加特性等方面给出大量实用建议,是所有程序开发人员必读之作

作者简介
Michael C. Feathers 世界级软件开发大师,就职于Object Mentor公司(这是一家世界领先的提供软件领域的指导、技能开发、知识传播和领导力服务的公司)。他是ACM和IEEE成员,也是CppUnit(从JUnit移植到C++上的单元测试框架)和FitCpp(FIT集成测试框架在C++上的实现)的最初作者,曾3次担任OOPSLA会议的CodeFest主席。目前他在世界范围内提供测试驱动开发、重构、面向对象设计、Java、C#、C++以及极限编程方面的培训和指导。

译者简介
侯伯薇 中荷人寿保险有限公司高级系统分析师,InfoQ中文站翻译团队主编,拥有十多年开发经验,目前致力于技术与业务的融合,让开发出来的程序能够真正提高业务人员的工作效率。热衷于通过翻译和演讲的方式与广大程序员分享和交流,曾翻译过多本技术书籍和几百篇技术短文,并在Scrumgathering、QClub、敏捷之旅等活动上做过技术演讲。

目录
目  录
译者序

前言
第一部分 修改机制
第1章 修改软件 2
1.1 修改软件的四大原因 2
1.1.1 增加特性和修正缺陷 2
1.1.2 改善设计 4
1.1.3 优化 4
1.2 组合在一起 4
第2章 利用反馈 7
2.1 什么是单元测试 9
2.2 高层次测试 11
2.3 测试覆盖 11
2.4 遗留代码修改方法 14
2.4.1 确定变更点 14
2.4.2 找到测试点 14
2.4.3 打破依赖关系 14
2.4.4 编写测试 15
2.4.5 做出修改并重构 15
2.5 本书其他部分 15
第3章 感知和分离 16
3.1 伪协作程序 17
3.1.1 伪对象 17
3.1.2 伪对象的两面 20
3.1.3 伪对象总结 20
3.1.4 模拟对象 21
第4章 接缝模型 22
4.1 大片的文本 22
4.2 接缝 23
4.3 接缝类型 25
4.3.1 预处理接缝 26
4.3.2 链接接缝 28
4.3.3 对象接缝 31
第5章 工具 36
5.1 自动化重构工具 36
5.2 模拟对象 38
5.3 单元测试用具 38
5.3.1 JUnit 39
5.3.2 CppUnitLite 40
5.3.3 NUnit 41
5.3.4 其他xUnit框架 42
5.4 一般测试用具 42
5.4.1 集成测试框架(Framework for Integrated Test,FIT) 42
5.4.2 Fitnesse 43
第二部分 修改软件
第6章 时间很紧张,但还需要修改 46
6.1 新生方法(Sprout Method) 48
6.2 新生类(Sprout Class) 50
6.3 包装方法 54
6.4 包装类 57
6.5 小结 61
第7章 永远都无法完成的修改 62
7.1 理解 62
7.2 延迟时间 63
7.3 打破依赖关系 63
7.4 构建依赖关系 64
7.5 小结 67
第8章 如何添加新特性 68
8.1 测试驱动开发 68
8.1.1 编写失败的测试案例 69
8.1.2 对其进行编译 69
8.1.3 使其通过 69
8.1.4 去除重复的内容 70
8.1.5 编写失败的测试案例 70
8.1.6 对其进行编译 70
8.1.7 使其通过 71
8.1.8 去除重复的内容 71
8.1.9 编写失败的测试案例 71
8.1.10 对其进行编译 71
8.1.11 使其通过 72
8.1.12 去除重复的内容 73
8.2 根据差异编程 74
8.3 小结 81
第9章 无法把类放到测试用具中 82
9.1 恼人的参数 82
9.2 具有隐藏依赖的情况 88
9.3 构造Blob的情况 90
9.4 恼人的全局依赖 92
9.5 可怕的Include依赖 99
9.6 洋葱皮参数 102
9.7 别名参数 104
第10章 无法在测试用具中运行方法 107
10.1 隐藏方法的情况 107
10.2 “有帮助的”语言特性 110
10.3 检测不到的副作用 112
第11章 我需要修改代码,应该测试哪些方法 119
11.1 推断影响 119
11.2 正向推理 124
11.3 影响传播 128
11.4 推理影响的工具 129
11.5 从影响分析中学习 131
11.6 简化影响草图 132
第12章 我需要在一个地方做多处变更,需要为所有涉及的类打破依赖关系吗 134
12.1 拦截点 135
12.1.1 简单的情况 135
12.1.2 更高层次的拦截点 137
12.2 使用夹点来判断设计 140
12.3 夹点陷阱 141
第13章 我需要修改代码,但不知道要编写哪些测试 142
13.1 鉴定测试 142
13.2 鉴定类 145
13.3 定向测试(Targeted Testing) 146
13.4 编写鉴定测试的启示 150
第14章 对库的依赖让我快要崩溃了 151
第15章 应用全是API调用 153
第16章 对代码理解不够,所以无法修改 160
16.1 做笔记,画草图 160
16.2 列表标记 161
16.2.1 分离职责 162
16.2.2 理解方法结构 162
16.2.3 提取方法 162
16.2.4 理解变更的影响 162
16.3 临时重构 162
16.4 删除没有用的代码 163
第17章 应用没有结构 164
17.1 讲述系统的故事 165
17.2 裸CRC 167
17.3 对话审查(Conversation Scrutiny) 170
第18章 测试代码挡路了 171
18.1 类命名规范 171
18.2 测试位置 172
第19章 项目并非面向对象,如何才能够安全地修改 174
19.1 简单的案例 174
19.2 困难的案例 175
19.3 增加新行为 178
19.4 充分利用面向对象 180
19.5 完全面向对象 183
第20章 类太大了,我不想让它继续膨胀 186
20.1 查看职责 188
20.2 其他技术 199
20.3 继续前进 199
20.3.1 策略 199
20.3.2 战术 200
20.4 提取类之后 201
第21章 在各个地方修改的都是同样的代码 202
第22章 我需要修改一个巨兽方法,但无法为其编写测试 218
22.1 巨兽的种类 218
22.1.1 无序方法 218
22.1.2 缠结的方法 219
22.2 使用自动重构支持来对付巨兽 221
22.3 手动重构挑战 224
22.3.1 引入检测变量 224
22.3.2 提取你所知道的内容 227
22.3.3 收集依赖 228
22.3.4 打破方法对象 229
22.4 策略 229
22.4.1 记下方法的概要 230
22.4.2 找到序列 230
22.4.3 首先提取到当前类 231
22.4.4 提取小段代码 231
22.4.5 准备好重做提取 231
第23章 如何知道没有造成任何破坏 232
23.1 超感编辑(Hyperaware Editing) 232
23.2 单一目标编辑 233
23.3 保留签名 234
23.4 依赖于编译器 236
23.5 结对编程 238
第24章 我要崩溃了,它不会再有任何改进 239
第三部分 打破依赖的技术
第25章 打破依赖的技术 242
25.1 调整参数 242
25.2 分解方法对象 245
25.3 完善定义 251
25.4 封装全局引用 252
25.5 暴露静态方法 257
25.6 提取并重写调用 259
25.7 提取并重写工厂方法 261
25.8 提取并重写getter方法 262
25.9 提取实现器 265
25.10 提取接口 269
25.11 引入实例委托器 274
25.12 引入静态设置器 275
25.13 链接替换 280
25.14 参数化构造函数 280
25.15 参数化方法 284
25.16 原始化参数(Primitivize Parameter) 285
25.17 上推特性 287
25.18 下推依赖 290
25.19 使用函数指针替换函数 293
25.20 使用getter方法替换全局引用 295
25.21 创建子类并重写方法 297
25.22 替代实例变量 299
25.23 模板重定义 302
25.24 文本重定义 305
附录 重构 307
术语表 311

序言
前  言
你还记得自己写过的第一个程序吗?我还记得。那是我在早期的PC上编写的一个很小的图形程序。我开始编程的时间比大多数朋友都要晚。当然,当我还是个孩子的时候就见过计算机。我还记得,当第一次在一个办公室里看到微型计算机的时候,我就被深深触动了,但很多年过去了,我甚至都没有机会坐在一台计算机前面。稍后,当我十几岁的时候,我的一些朋友买了一些最早期的TRS-80计算机。我非常感兴趣,但也非常不安。我知道一旦开始玩计算机,就会被它深深吸引。它看起来太酷了。我不知道为什么我对自己如此了解,但我还是把它延后了。之后,在大学中,我的一位室友有一台计算机,我买了一套C语言编译器,从而可以自学编程。就是从那一刻开始。我一夜又一夜地试着编写程序,使用编译器自带的emacs编辑器写出大量源代码。那让我非常上瘾,而且很有挑战性,我爱死它了!
我希望你也有过类似的经历——让代码能够在计算机上工作的那种最原始的快乐。几乎所有我问过的程序员都有。那种快乐是让我们从事这份工作的原因之一,但一天又一天,快乐在哪里呢?
多年前的一天晚上,在完成工作之后,我给朋友Erik Meade打了一个电话。我知道Erik刚刚开始为一个新团队提供咨询服务,于是我问他:“他们做得如何?”他说:“伙计,他们在编写遗留代码。”这让我心头一震,在我的生命中有过几次那样的情况,这就是一次。我从内心理解他的说法。Erik说的话非常准确地描述了我访问很多团队时所获得的第一感觉。他们非常努力,但在一天结束的时候,迫于时间的压力,繁重的历史包袱,或者缺少能够比较的更好代码,很多人都在编写遗留代码。
什么是遗留代码呢?这个词我使用了很长时间,但并没有准确的定义。让我们看一下严格的定义:遗留代码是我们从别人那里获得的代码,可能是我们公司从另一家公司获得的代码;也可能是最初某个团队迁移到另一个项目中的代码。遗留代码是别人的代码。但在程序员中,这个词有更丰富的含义。“遗留代码”随着时间推移拥有越来越多的含义。
当听到遗留代码这个词时,你会想到什么呢?如果你的情况和我类似,那么就会想到复杂、莫名其妙的结构,你需要修改但是无法真正理解的代码。你在思考中度过一个个不眠之夜,只为了增加特性,而那些特性本来应该很容易添加,然后你要考虑如何演示,团队中的每个人都非常讨厌那些代码,它们看起来根本没人管,你死都不愿意去碰那些代码。你们其中有些人只要想想要让它变好就难受。看起来你们的努力没有任何价值。代码会以多种方式变差,很多方式都和代码是否来自另一个团队没有关系。
在业界,遗留代码这个词通常代表的是我们根本就不理解的难以修改的代码。但是经过和团队一起工作多年,帮助他们解决严重的代码问题,我得到了不同的定义。
对我来说,遗留代码是缺少测试的简单代码。我为这个定义而感到有些悲哀。测试和代码的好坏有什么关系?对我来说,答案很简单,那也是我在本书始终详细阐述的一个观点。
缺少测试的代码就是很差的代码。不管它编写得有多好;也不管它的面向对象以及封装做得有多好。有了测试,我们可以快速且有保证地修改代码的行为。相反,没有测试,我们就不知道代码是变得更好还是更糟糕。
你可能认为这有些过分。那么整洁的代码呢?如果代码非常整洁、结构良好,那就足够了吗?好吧,不要错怪我。我喜欢整洁的代码,甚至比大多数我认识的人还要喜欢,但是尽管整洁的代码非常棒,那还不够。当团队试图做重大的修改,而缺少测试的时候,就会冒很大的风险。就像是在做空中体操而没有保护网一样。那需要非常优秀的技能,以及对每步会发生什么都有非常清晰的了解。精确地知道如果你修改一系列变量会发生什么,就像是你翻跟斗翻出去的时候,有另一个人会抓住你的胳膊。如果你所在的团队代码非常清晰,你的形势要比其他程序员更好一些。在我的工作中,我发现很少有团队的代码能够达到那种整洁程度。它们看起来是统计上的不正常现象。并且,你知道吗?如果你没有起支持作用的测试,那么修改那样的代码会比拥有测试的团队慢。
是的,团队会变得更好,并开始编写更加清晰的代码,但是想要让旧代码变得清晰,需要花费很长时间。在很多情况下,那永远都不会有根本性的改变。因此,我把遗留代码定义为缺少测试的代码,这是没有问题的。它是很有效的定义,并且指出了解决方案。
到现在为止,我说了很多关于测试的事情,但本书并不是关于测试的一本书。本书是帮助大家有信心修改任何代码的书。在接下来的章节中,我会描述可以用来理解代码、为其编写测试、重构以及增加特性的技术。
当阅读这本书的时候,你会注意到的一点是,这并不是一本关于完美代码的书。我在书中使用的代码都是虚构的,因为我的工作需要和客户签订保密协议。但在很多例子中,我试图保持在那个领域所见到的代码的内在品质。我不想说例子总是很有代表性。肯定会有更好的代码,但坦白说,也总是会有一些代码,比我在本书使用的例子要差得多。除了需要为客户保密之外,我之所以不能把那样的代码放到本书中,也是为了不让你厌烦,而忽略隐藏在繁冗的细节中的重要问题。因此,很多例子都相对比较简洁。如果你看到一段代码,想:“不,他根本不知道,我的方法要比那大得多,也差得多”请看一下我给出的建议,看它是否适用,即便例子看起来更简单一些。
本书中所讲述的技术已经在大型代码中经过检验,只是由于篇幅所限,例子才都比较小。特别是当你在代码片段中看到这样的省略号(…)的时候,你可以把它读作“在此插入500行丑陋的代码”:

本书不是关于完美代码的,甚至也不是关于完美设计的。好的设计应该是我们所有人的目标,但在遗留代码中,那是我们无法完全达到的。在某些章节中,我描述了向现有代码中增加新代码的方式,并展示了如何在添加的时候在头脑中想着好的设计原则。你可以先在遗留代码库中开辟出优秀代码区域,但是,如果你想要采取的变更步骤让代码更加丑陋,也不要感到惊奇。这项工作就像是外科手术。我们需要切开皮肤,穿过内脏,这时可能暂时不要考虑美观的问题。这位病人的主要器官和内脏能更好一些吗?是的。那么我们能不管最直接的问题,把他的伤口缝合,然后告诉他正确饮食,然后就训练他开始跑马拉松吗?我们可能可以,但我们真正需要做的就是正视病人的情况,治好有问题的部分,让他更加健康。他可能永远都无法成为奥运会运动员,但是我们不能让“最好”成为“更好”的敌人。代码库可以变得更健康,更容易在其中工作。当病人感觉好一些之后,就到了你可以帮助他进入更健康的生命循环中的时候了。那正是我们对遗留代码所应该做的事情。我们试图回到让我们感觉舒服的那个点;我们期望那样,并试图让修改代码更容易。当我们可以在团队中保持那种感觉的时候,设计就会变得更好。
我在本书中所描述的技术,都是多年以来和客户一起协作试图对无规则的代码加以控制的过程中发现和学到的。我偶尔还会强调遗留代码。当我最初开始在Object Mentor公司工作的时候,我的工作包括帮助团队解决严重的问题,改善他们的技能和交互,帮助他们达到能够持续交付高质量代码的程度。我通常会使用极限编程实践来帮助团队掌控他们的工作,紧密协作,然后交付。我常常觉得极限编程不仅仅是开发软件的一种方式,更应该是创造出好的团队形式的方式,只是恰好以每两周一次的频率交付了优秀的软件。
但是,从最开始就有一个问题。很多最初的XP项目都是从头开始的项目。我见到的客户都拥有非常庞大的代码库,正处于麻烦之中。他们需要某种方式来掌控他们的工作并开始交付。随着时间推移,我发现自己总是和客户一次又一次做着同样的事情。这种感觉在我和一个开发金融项目的团队协作时达到了顶峰。在我参与之前,他们就已经意识到,单元测试是非常好的东西,但是他们所执行的测试是完整场景的测试,会与数据库做多次交互,并执行大量代码。因此非常难以编写测试,而且团队不会经常运行它,因为运行一次要花费太长时间。当我和他们坐在一起打破依赖关系,并为小段代码编写测试的时候,我有一种似曾相识的可怕感觉。看起来我和所有见过的团队都是在做这种工作,而那是一种没有人想要考虑的东西。那是一种乏味的工作,当你想要以可控的方式来处理代码而且知道如何做的时候,才会去做。然后,我决定总结一下我们会如何解决这些问题,并把它们写下来,从而帮助团队让他们的代码更容易处理,那会很有价值。
关于例子还要说明一下:我使用了多种不同编程语言编写的示例。那些例子是由Java、C++和C语言编写的。我选择Java是因为它是一种非常常见的语言,包含了C++的例子是因为它在遗留环境中代表了某些特殊的挑战,而选择C语言是因为它凸显了很多在过程化遗留代码中所存在的问题。通过这三种语言,我覆盖了在遗留代码中出现的大多数问题。然而,如果你所使用的语言并没有出现在示例代码中,那么也要阅读那些代码。我讲述的很多技术都可以用在其他语言中,如Delphi、Visual Basic、COBOL以及FORTRAN。
我希望你会发现本书中的技术很有帮助,让你可以找回编程的乐趣。编程可以是一种回报丰厚并且让人享受的工作。如果你在日常的工作中没有感觉到那一点,我希望我在本书中提供给你的技术能够让你找到那种感觉,并在团队中传播。
如何使用本书
我试过用多种不同的形式来写这本书,直到最后确定为现在的这个样子。当处理遗留代码的时候,很多不同的技术和实践都非常有用,很难独立开来说明。如果你能够找到接缝、创建伪对象、使用一些技术打破依赖关系,那么最简单的修改会变得更加容易。为了让本书更易于理解,随时可用,我决定把很大一部分内容(第二部分)以FAQ(经常问到的问题)的形式来组织。因为特定的技术通常需要使用其他技术,FAQ章节相互之间的关联性很强。在几乎每章中,你都会看到带有章节号的引用,你可以查看其他章节和部分,其中会描述特定的技术和重构。如果这让你在试图寻找问题答案的时候,不得不大范围翻阅本书,那么我非常抱歉,但我认为你最好那么做,而不是一页接一页地阅读,试图理解所有技术是怎么做的。
在第二部分,我试图回答在处理遗留代码的过程中所遇到的常见问题。每章的标题都是一个特定的问题。这让章节的名称很长,但希望那可以让你快速找到帮助你解决特定问题的部分。
在修改软件部分的前面,是一系列介绍性章节(第一部分),后面是一系列重构列表(第三部分),那在处理遗留代码的工作中非常有用。请阅读介绍性章节,特别是第4章。这些章为之后的技术提供了具体情境和命名法。此外,如果你发现没有在上下文中描述的术语,那么就可以在术语表中查找。
第三部分中的重构比较特殊,因为它们是故意在没有测试的情况下完成的,目的是为了编写测试。我建议你阅读所有内容,从而可以在开始驯服遗留代码的时候看到更多的可能性。
致谢
首先,非常感谢我的妻子Ann以及孩子Deborah和Ryan。他们的爱和支持让这本书以及所有的学习成为现实。我还要感谢“Bob大叔”Martin,他是Object Mentor公司的主席和创始人。他严密而有效的开发和设计方法能够把重要内容从无关紧要的内容中分离出来。大概十年前,我快要被大量不切实际的建议所淹没,正是他带领我走出了那段困境。还要感谢Bob给了我机会看到更多代码,并在过去五年间和更多人一起工作,我从来没有想象过能够那样。
我还要感谢Kent Beck、Martin Fowler、Ron Jeffries和Ward Cunningham,他们经常会给我提出建议,并且教给我很多关于团队工作、设计和编程方面的知识。特别要感谢所有审阅了初稿的人。正式审稿人有Sven Gorts、Robert C. Martin、Erik Meade和Bill Wake;非正式审稿人有 Robert Koss博士、James Grenning、Lowell Lindstrom、Micah Martin、Russ Rufer和硅谷伙伴小组以及James Newkirk。
还要感谢审阅了我早期放在网上的书稿的人们,他们的反馈在我重新组织了形式之后,对书的方向产生了很大影响。我先要说声抱歉,因为名单可能会有所遗漏,他们是:Darren Hobbs、Martin Lippert、Keith Nicholas、PhlipPlumlee、C. Keith Ray、Rober Blum、Bill Burris、William Caputo、Brian Marick、Steve Freeman、David Putman、Emily Bache、Dave Astels、Russel Hill、Christian Sepulveda和Brian Christopher Robinson。
还要感谢Joshua Kerievsky,他做了非常关键的早期审阅工作;感谢Jeff Langr,他在我写这本书的整个过程中给出了很多建议,并做了大量审阅工作。
审稿人让我的草稿变得更加合理,但如果还有错误的话,那都是我自己的问题。
感谢Martin Fowler、Ralph Johnson、Bill Opdyke、Don Roberts和John Brant在重构领域做出的卓越工作。那给了我很大启发。
特别要感谢Jay Packlick、Jacques Morel、Sabre Holdings的Kelly Mower、Workshare Technology的Graham Wright,他们都提供了很多支持和反馈。
还要特别感谢Paul Petralia、Michelle Vincenti、Lori Lyons、Krista Hansing,以及Prentice-Hall团队中的所有人。谢谢Paul,你给了我这个第一次写书的人太多帮助和鼓励。
特别感谢Gary和Joan Feathers、April Roberts、 RaimundEge博士、David Lopez de Quintana、Carlos Perez、Carlos M. Rodriguez、John C. Comfort博士,多年来你们给了我大量帮助和鼓励。我还要感谢Brian Button帮忙提供了第21章中的示例,当我们一起开发重构课程的时候,他花费了一个小时来编写那段代码,那是我最喜欢的一段课程用代码。
另外,还要特别感谢Janik Top,他的乐曲De Futura在写这本书的最后几个星期中一直陪伴着我。
最后,我还要感谢过去多年间一起工作的所有同事,他们的深刻见解和挑战让书中的内容更加充实。

Michael Feathers
mfeathers@objectmentor.com
www.objectmentor.com
www.michaelfeathers.com

购买书籍

当当网购书 京东购书 卓越购书

PDF电子书下载地址

相关书籍

搜索更多