`
czjxdm
  • 浏览: 124999 次
社区版块
存档分类
最新评论

读《修改代码的艺术》有感

阅读更多
最近一直在读《修改代码的艺术》一书,体会挺深的。陆续记录自己的心的体会。

本书英文原名《Working Effectively with Legacy Code》,大体意思是有效的面对遗留代码,但是不知道为何被翻译为修改代码的艺术,而我觉得本书所讲述的内容并不是关于修改代码的具体细节,更没有太多艺术感。讲述怎么做好单元测试,但是这丝毫不影响这本书的价值,以及方法的可行性

2.本书的主要内容
提前说明一个概念,遗留代码:已有的项目代码,不管是你的,还是他人的,不管是维护中的,还是开发中的,总之是已经写好的代码,称为遗留代码。当代码被写下来,编译通过,并checkin后,它就变成了遗留代码。
本书主要讲的是在对遗留代码进行修改前,要进行的准备工作,即安置单元测试,保护当前代码的已有行为,并在此基础上引入测试驱动开发。
如何把测试安置到遗留代码中,并不是一件简单的事情,本书正是为了让我们做到这件不简单的事情而准备的,因此把本书看作测试驱动开发实践的前哨和准备也不为过。

3.安置测试难在哪里?
简单的单元测试说白了很简单,实例化一个类,然后调用被测试方法,然后验证测试结果。但现实很复杂,一个不良的类很难实例化,当你实例化这个类的时候,往往要实例化另外一个被依赖的类,如果该类涉及I/O,网络,数据库什么的,难道还要为了单元测试配置环境不成?
另外一个难处是难以调用被测试方法,因为该方法可能会显性的(通过参数)或者隐性的(全局变量)依赖于一些难以实例化的类和对象。
最后一个难处是如何感知测试结果并验证,有返回值的函数还可以判断个返回值什么的,没有返回值的函数怎么才知道它是正确的呢?
以上这三个问题,我是没想到如何解决,在我早期接触单元测试这个概念的时候。

4.如何安置测试
要安置测试首先必须找出测试点,当难以用相关技术在测试点安置测试的话,采取退一步的策略是比较划算的。
当确定测试点后,为了在测试点安置测试,首先要进行的就是解依赖。解依赖的目的是分解出当前的类对其他难以实例化的类和全局变量过分紧密的依赖,使得被测试的类容易在测试工具中实例化,被测试方法容易调用。
要解依赖,就要利用被测试代码中的接缝,在测试工具中分离出难以实例化的依赖。书中对接缝是这样定义的:“是指程序中的一些特殊的点,在这些点上你无需做任何修改就可以达到改动程序行为的目的。”。
举一个接缝的简单例子,如虚函数,你无需对原来的代码做任何改动,就可以通过继承并重载来改变程序的行为。
遗留代码也许并没有多少接缝,因此为安置测试而解依赖的主要目的就是将依赖放置到接缝下,并在测试工具下改变接缝的行为,使得能够实例化被测试类,以及调用被测试方法,并通过接缝感知和验证测试结果。
总的来说,安置测试的过程如下:
      4.1找出测试点
      4.2解依赖:用接缝包住依赖
      4.3在测试工具下改变接缝行为
      4.4简单的执行单元测试:实例化类,调用被测试方法
      4.5验证测试结果,通过接缝验证测试结果(需要的话)

5.接缝(解依赖技术)
本书一共给出24种解依赖技术,不过我感觉其实很多是已有方法的变种,因此总结了一下,常用的有4种:虚函数并子类化,设置并替换,获取方法,参数化方法。特殊的有2种:参数包装,方法对象。

6.《修改代码的艺术》,这里有什么好修改的?
这本书能和修改细节扯上边的地方,就是解依赖技术了。为了提供接缝,就必须在没有测试保护的情况下对遗留代码进行修改,因此最好采取尽量安全的修改方式。本书给出了为了解依赖,制造接缝而如何进行安全修改的修改细节,当然简单的说无非就是签名保持和剪切粘贴之类的了。



本书内容关于如何有效处理遗留代码,遗留代码是指没有编写测试的代码。因此,为遗留代码编写测试是改善遗留代码的首要任务。对一个大系统,不可能从头开始编写每一处的单元测试,一般只能从当前需要改动的地方开始,逐步添加单元测试,形成“软件夹钳”,进而修改并改善现有代码。遗留代码修改算法:   

(1) 确定改动点;(前提:理解代码)

(2) 找出测试点;(前提:理清代码间的联系)

(3) 解依赖;(解依赖是为类编写单元测试的前提 )

(4) 编写测试; (编写符合代码当前行为的特征测试 )

(5) 修改、重构。 (在存在测试覆盖的前提下,修正bug 、改善设计等 )

从上述算法可以看出,前4 条是关于如何编写测试代码的,而解依赖是编写测试的前提,因为本书很大程度可以说成是关于如何解依赖的书籍,书中也用来很大的篇幅来介绍解依赖技术。当然,解依赖除了处理遗留代码,还可用于指导编写易测试的代码。




测试代码的命名约定:(测试类:DBEngine )

单元测试类:一个类至少要编写一个相应的单元测试类,故单元测试类常常在目标类名上加 “Test” 前缀或后缀。为便于浏览,加后缀方便些。DBEngineTest

伪类(伪对象或仿对象):伪类是指用于测试的伪造类。 伪类常用”Fake” 作为前缀,使得所有的伪类都在一起,便于区别。FakeDBEngine

测试子类(testing subclass ): 测试子类是指利用继承将不关心的行为架空使只访问测试所关心的行为的派生子类。 测试子类常由子类化并重写方法技术生产。测试子类本质就是为测试类接触不必要的测试依赖。测试子类常使用“Testing ”前缀。TestingDBEngine

 

遗留代码工作的三个关键概念:感知、分离和接缝 。

感知和分析和解依赖直接相关,解依赖是将类放入测试用具的重要手段( 有时是唯一手段) ,因为类之间往往是相互依赖,相互影响的,为了能单独测试某类,我们需要接触类之间的依赖关系,尤其是测试类所依赖的类。很多时候解依赖唯一的办法就是通过伪装成被影响的类来直接感知所受到的影响。感知和分离式解依赖的两个目的:1) 感知;当无法感知代码的状态 ( 测试) 时,通过解依赖来感知“状态”;2) 分离:当无法将代码放入测试用具时,通过解依赖来分离测试代码。

类难于测试的根本原因在于:类很少是单独存在的,往往是相互依赖。所要测试的类往往存在如下依赖关系:1) 实例对象(成员变量); 2) 委托对象(接口参数); 3) 临时对象(所创建或实例化的任何对象); 4) 全局对象(单体,系统或库API )。

注:上述对象不包括基本类型和基本对象(如STL 等标准库)。

所有的对象都存在自己的逻辑,从而如果想要编写单独的类测试用例就应该接触对这些对象的依赖。下面一一给出解决方案。

1) 实例对象(成员变量):使用伪对象

2) 委托对象(接口参数):使用伪对象

3) 临时对象(所创建或实例化的任何对象):使用伪对象

4) 全局对象(单体,系统或库API ) :以获取方法替换全局引用;封装API ,形成内部调用接口,通过子类化并重写方法,屏蔽不必要的系统API 调用。

 

使用伪对象的解依赖技术:

参数化构造函数:直接传入伪对象。

参数化方法:避免临时对象的硬编码,直接传入伪对象

引入实例委托:避免全局对象的硬编码,直接传入伪对象

替换实例变量:增加实例变量设置接口,不推荐。

引入静态设置方法:用于替换静态对象, 如单体实例。

参数适配:将依赖的参数类型替换成可伪装的自定义类型。

实现提取:用于提取抽象基类或接口 ,进而定义伪对象Fake

接口提取:用于提取抽象基类或接口,进而定义伪对象

 

接触类内部依赖的解依赖技术:

子类化并重写方法 :将少数无须测试的内部接口重写,通过实例化重写的派生类来测试原有类的接口。这里的重写是指虚函数的重实现,而非覆盖。

提取并重写调用:封装API ,并重写该接口

提取并重写工厂方法:用于实例化伪对象,避免更改接口,本质就是替换实例变量。

提取并重写获取方法:延迟获取实例变量,用于C++ 。因为C++ 在构造函数中虚函数机制被禁止,故无法再构造函数中调用子类重写的工厂方法。

 

特定情况下的解依赖技术:

分解出方法对象:重构巨型方法

朴素化参数:避免参数依赖

封装全局引用:

以获取方法替换全局引用:利于重写获取方法。

定义补全:C/C++ 的定义和实现是分开的,通过重写实现替换原有行为。

连接替换:利用链接期接缝,替换库,DLL 等,实现行为替换。

暴露静态方法:避免对象的实例化,用于难实例化的对象。

换函数为函数指针:用于C 的行为替换,不推荐。


单元测试及可测性方面:
1、单元测试需要验证的两个方面:状态,行为。当测试一个对象的方法时,需要验证方法的执行是否影响自己的状态,是否会调用依赖对象的行为

2、单元测试的控制点与检查点,控制点包括直接输入(参数),间接输入(控制被调用函数的返回值);检查点包括直接输出(返回值,状态),间接输出(行为)

3、单元测试并不是指白盒测试,单元测试同样用到黑盒中的等价划分,边界值等技术,也用到白盒中技术提高代码覆盖率。所以白盒称之为结构测试更合理,而黑盒称之为行为测试更合理

4、测试替身:Dummy Object:主要用于类的构造或方法的调用需要一个符合类型的对象,但对该对象的状态或行为并不关心时;Test Stub:测试对象利用它返回值,让测试对象期望的行为发生;Test Spies:接收测试对象方法的调用,用于行为检查;Mock Objects:即可充当stub,也可充当spy;Fake Objects:生成一个专用的测试类,应用于测试对象依赖类还未实现或过于复杂时

5、在TDD中有一个FIRST原则(同样适用于单元测试的编写)

为什么要求测试运行时间少于1秒?主要为了使对象尽可能的无依赖。不过段念的团队也没实行TDD,而是先编码再单元测试,不过在编码时就考虑到可测性

6、通过重构的技术来提高可测性,如接口提取,依赖注入

7、提高可测性:感知:感知某种方法调用产生的效果或影响(采用mock);分离:与应用的其他部份分离开来单独运行(解耦)


无法将类放入测试用具中

(1):无法轻易创建该类的对象
(2):当该类位于测试用具中是,测试用具无法轻易通过编译构建
(3):我们需要用到的构造函数具有副作用
(4):构造函数中有一些要紧的工作,我们需要感知到他们

9.1 : 另人恼火的参数

public Class CreditValidator {
    public CreditValidator  () {

}

}

4 实行单元测试
4.1 现实困难
1、 内部依赖问题

类之间相互协作共同完成功能,类之间的依赖必不可少。为了测试某个类,必须实例化它依赖的类,而它依赖的类又可能依赖其他类,因此必须实例化其他类。如此一环扣一环,可能把整个项目大部分类都包含在了这次测试中,最后做的不是单元测试,而是挂着单元测试外壳的集成测试。

2、 外部依赖问题

很多项目,尤其是我们的网络应用服务器,运行期间需要依赖外部的其他服务器或者数据库或者本地的文件系统。而对很多外部的依赖很难模拟,或者说模拟成本太高,往往让测试者望而却步。

3、 函数本身问题

项目中的很多或者可能是大部分函数,是没有明确返回值或者无异常抛出,而只是和其他外设交互。难于使用测试断言判定。

以上造成很难将某个类从项目中隔离出来,难以设置单元测试点。

4.2 解决困难
上述困难均为依赖造成。

1、内部解依赖

对被测代码进行解依赖,强化设计,减少耦合,提高代码可测性。解依赖的过程也即为对代码重构过程,减少类间耦合,制造接口层。常用手段有:虚函数、函数指针、传递参数等方式。而对于难于进行解依赖情况,就要考虑提取分化重写方法。

2、写桩代码模拟外部环境

单元测试不能直接依赖外部环境,必须写桩代码模拟。而外部环境的可模拟性与内部解依赖紧密相关。对于外部的随机性和各种不确定性,桩代码必须尽可能模拟。

3、开辟访问类私有属性通道

有些类方法虽然没有明确返回值,但可能修改类的内部状态,可以通过判断类的私有属性来判定类方法的执行情况。可以给类增加Get()方法或者将私有属性设置为protected。

更重要的是在代码开发期,引入TDD思维,强化设计,提高代码可测性,提高代码的整体质量



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/CppExplore/archive/2010/06/28/5698526.aspx


http://blog.csdn.net/zhenjing/archive/2009/09/27/4602327.aspx

treeVIewer 树
http://space.itpub.net/13081368/viewspace-364829

http://www.josdoc.com/html/ceshi/EasyMock/
分享到:
评论

相关推荐

    编程界公认的艺术品程序

    首先,当我们谈论“艺术品程序”,我们无法避开一个核心要素——优雅的代码。一个优秀的程序,其代码结构必须是清晰的,它要遵循良好的编程规范,使得阅读代码如同欣赏诗篇一样愉悦。这种代码的美感来自于程序员对于...

    C语言的科学与艺术(配书)

    通过对比不同的代码版本,读者可以学习到如何逐步完善程序,如何编写出更健壮、更可读的代码。 在这个过程中,读者不仅仅是学习了C语言的技术细节,更重要的是培养了解决问题的能力和创新思维。《C语言的科学与艺术...

    《清明上河图》艺术赏析修改.zip

    《清明上河图》是北宋时期画家张择端创作的一幅长卷风俗画,被誉为中国十大传世名画之一,是中国绘画史上的一颗璀璨...通过《清明上河图》艺术赏析修改.doc这份文档,读者将能更深入地理解这幅不朽名作的内涵与魅力。

    儿童节快乐庆祝代码教程

    "儿童节庆祝.pdf"文件很可能是这个教程的详细文档,其中可能包含了代码的逐行解析、运行环境的设置指南、以及如何运行和修改代码的说明。对于初学者,这样的文档可以帮助他们更好地理解和学习代码。 此外,这个教程...

    用单元测试驯服烂代码-2013.03.09

    Feathers在《修改代码的艺术》中指出,遗留代码通常是没有编写相应测试的代码。在此基础上,伍斌进一步定义烂代码为内在质量低下的代码,具体表现为难以理解、难以测试以及难以扩展。 ##### 为何代码会变烂 代码变...

    图片演示代码.rar

    "全屏缩放广告代码下载.rar" 涉及到的是全屏背景图片的动态缩放效果,常用于营造大气、沉浸式的用户体验,尤其适用于摄影、艺术或者产品展示类网站。 "随图片快速翻动幻灯代码下载.rar" 展示的是图片快速翻转的动画...

    520节日表白代码资源.zip

    这份文档可能还包含了代码结构的概述,便于用户理解每个部分的功能,以及如何根据自己的需求进行修改和定制。对于初学者来说,这样的说明文档是十分宝贵的,因为他们可以借此学习到如何将编程技能应用于实际情境,...

    很好看的精美FLASH图片展示源代码

    这套源代码的主题为“很好看的精美FLASH图片展示”,从标题中我们就可以感受到开发者所倾注的心血和对视觉效果的重视。它不仅仅是一个简单的图片展示工具,更是一个能够创造出流畅过渡效果、动态用户体验和精心设计...

Global site tag (gtag.js) - Google Analytics