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

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

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

本书英文原名《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/
分享到:
评论

相关推荐

    Javaee的影视创作论坛BS_sql server 设计软件源码+数据库+WORD毕业论文文档.zip

    基于Javaee的影视创作论坛的设计与实现的源代码和论文由学员提供.... 基于Javaee的影视创作论坛的设计与实现主要用功能包括: 首页推荐、用户管理、影片管理、评论管理、 预告片管理、海报管理、公告管理、数据...

    第一个“活机器人”开源项目 Robi-Transform-Project-master.zip

    您可以播放,更改代码,再试一次。虽然粗糙但基本上满足了玩它的需要(子功能也需要各种改进)。 什么是“活机器人”? 想象一下,如果你有一只兔子或一只蜥蜴作为宠物,它们绝不会卖给你可爱的,也不会回答你的...

    C++标准库介绍.pdf

     开放源代码GUI库作者从自己亲身开发经验中得出了个理想GUI库应该是什么样子感受出发从而开始了对 这个库开发有兴趣可以尝试下 5、WTL  基于ATL个库使用了大量ATL轻量级手法模板等技术在代码尺寸以及速度优化方面...

    大、小断层矿井小波SVM融合智能故障预测matlab代码.zip

    1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    垂直SeekBar(拖动条).zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    libADLMIDI1-1.5.0-bp153.1.1.x86-64.rpm

    libADLMIDI1-1.5.0-bp153.1.1.x86_64.rpm 是用于在 x86_64 架构的设备上安装的 RPM 包,具体功能如下: 名称:libADLMIDI1 版本:1.5.0 摘要:带有 OPL3 (YMF262) 模拟器的软件 MIDI 合成器库 许可证:GPL-3.0-only 和 LGPL-3.0-only 该库提供了一个基于 ADLMIDI 的软件 MIDI 合成器,它模拟了 OPL3 音源芯片(FM 合成)。它可以通过使用 ADLMIDI 库来实现多平台的 MIDI 播放和 OPL3 模拟。 该 RPM 包适用于 x86_64 架构,用于在相关设备上安装 libADLMIDI1 库文件。库文件包括: /usr/lib64/libADLMIDI.so.1 和 /usr/lib64/libADLMIDI.so.1.5.0:库文件 /usr/share/doc/packages/libADLMIDI1/AUTHORS、/usr/share/doc/packages/libADLMIDI1/README.md 等文档文件:文档文件

    基于qt+C++实现u盘插拔检测.+源码(毕业设计&课程设计&项目开发)

    基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    Quectel_Product_Brochure_CN_V7.9.pdf

    Quectel_Product_Brochure_CN_V7.9.pdf

    更换软件主题(apk方式).zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    chepai-reg-main (2).zip

    phpstudy

    Python 入门详细教程-1天学会 Python.docx

    python入门

    二维码扫描的实现.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    移动机器人机械臂的设计开题报告.doc

    移动机器人机械臂的设计开题报告.doc

    基于QT+C++开发的智能平台访客系统+源码

    用法链接:https://menghui666.blog.csdn.net/article/details/137977678?spm=1001.2014.3001.5502 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。

    三菱机械臂校点说明.pptx

    三菱机械臂校点说明.pptx

    按字母索引滑动.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    激光推送客户端demo.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    c语言入门,小白进军C语言.zip

    C语言诞生于美国的贝尔实验室,由丹尼斯·里奇(Dennis MacAlistair Ritchie)以肯尼斯·蓝·汤普森(Kenneth Lane Thompson)设计的B语言为基础发展而来,在它的主体设计完成后,汤普森和里奇用它完全重写了UNIX,且随着UNIX的发展,c语言也得到了不断的完善。为了利于C语言的全面推广,许多专家学者和硬件厂商联合组成了C语言标准委员会,并在之后的1989年,诞生了第一个完备的C标准,简称“C89”,也就是“ANSI C”,截至2020年,最新的C语言标准为2018年6月发布的“C18”。 [5] C语言之所以命名为C,是因为C语言源自Ken Thompson发明的B语言,而B语言则源自BCPL语言。 1967年,剑桥大学的Martin Richards对CPL语言进行了简化,于是产生了BCPL(Basic Combined Programming Language)语言。

    Python入门到精通.zip

    python入门 单元测试和测试用例 Python标准库中的模块unittest提供了代码测试工具。 单元测试用于核实函数的某个防霾呢没有问题; 测试用例是一组单元测试,这些单元测试仪器一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种收入,包含所有针对这些情形的测试。 全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。 对于大型项目,要实现全覆盖可能很难。通常,最初只要对针对代码的重要行为编写测试即可,等项目给广泛使用时再考虑全覆盖。 可通过的测试 创建测试用例的语法需要一段时间才能习惯,但测试用例创建后,再添加针对函数的单元测试就很简单了。要为函数编写测试用例,可先导入模块unittest以及要测试的函数,在创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。 下面test_name_function.py一个只包含一个方法的测试用例,它检查函数get_formatted_name()在给定名和姓时能否正确的工作。

Global site tag (gtag.js) - Google Analytics