尽管编程语言爆发式涌现,C++依然顽强地战斗着。参考2013年7月的Tiobe统计,C++是排名第四的最流行编程语言。(最新的统计,请查看http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html,译者:2015年8月C++回到了第三名。) 2011 ISO标准(ISO/IEC 14822:2011,简称C++11)为C++带来了很多新特性,提高了它的接受度,最少,软化了反对它的态度。
C++依然是构建高性能方案的最佳选择之一。如果你公司的产品集成了硬件,或许你有一个相当规模的C++系统。如果你公司在90年代或更早就已经成立,或许你有一个长期工作的C++系统,或许它非常好,在接下来的数年,它也不会消失。
假设你正在用C++工作,你可能会思考以下问题:
就我个人来说,我最开始使用C++是在90年代初,在很多东西都没有之前,比如模板(还有模板元编程!),RTTI,STL以及Boost。从那时起,我不得不多次回归这门强大的语言,但是以些许沮丧告终。就像其它语言,C++允许你“打飞自己的脚”,但是使用C++,你常常无法察觉你打中了自己,至到一切都太晚了。你很可以比那些使用其它语言工作的家伙少一些脚指头。
如果你使用C++工作多年,你可能已经进化出许多成例和实践以帮助你保持高质量的代码。顽固的C++老手也许是程序员中最小心谨慎的,因为与C++一起挣扎的足够长,这需要对代码工艺一丝不苟的小心和注意。
如此小心谨慎,有人会认为C++系统的代码质量应该会很高。然而,多数C++系统表现出了同样的问题,这些问题我们反复发现,通常与语言并不相关。
这些腐败问题难以避免?不是,测试驱动开发是一种易学易用的工具,它可以帮助你阻止系统熵增。它也可以激起你编程的激情。
如果你只是简单的寻求报酬,市面上有大量C++工作可以保证你被雇用。但是,C++是一种高阶技巧和微妙的语言。大意的使用它会导致缺陷,间歇地出错,或者多天的调试之旅,这些会高度危胁你的报酬。TDD能帮助你。
为这些巨大,长期工作的C++系统添加新功能所需的工作经常令人失望,或者难以预测。为了修改其中几行代码,仅仅理解这段代码就会花去数小时,甚至几天。生产率会被拉的更低,因为开发人员需要等几小时来编译改动的代码,需要更多的时间来验证改动,以确久它正确的与系统的其它部分集成了。
这个过程并不是必须这样。90年代后期诞生的软件设计一种技术,测试驱动开发可以帮助你制服C++系统,并在添加新功能过程中,保持它稳定。(注意,编码前编写测试的时间可能相当长。TDD过程详细和正式的阐述在Ward Cunningham和Kent Beck编写的《Test Driven Development: By Example[Bec02])。
本书的主目标是帮助你学习TDD的正规方式,以便在真实的应用中实践。你将学到你以下内容: - TDD的基础原理 - TDD可能的收益。 - TDD如何帮助你解决设计缺陷,在它出现时。 - TDD的挑战和代价 - TDD如何减少,甚至消除调试过程。 - 如何长期保持TDD。
“单元测试值得这么折腾吗?它看起来帮不上我什么。”
你也许已经试过单元测试。也许你正在挣扎着为遗留系统编写单元测试。也许TDD看起来只适合那些足够幸运,可以工作在一个崭新系统上的极少数人。但是,你工作在一个根生地固,极富挑战的系统上,TDD能解决你日复一日地问题码?
确实,TDD是一个有用的工具,但不是处理遗留系统的银弹。在你以测试驱动的方式增加新功能到系统中时,你同时也城同要切除多年来积累的坏东西。你需要增加一些策略和技巧构成持续清晰的方法。你需要学习解除依赖的技巧和安全修改代码的方法,正如 Michael Feathers 在他的重要著作《Working Effectively with Legacy Code [Fea04]》中展示的一样。你需要知道如何完成大规模的重构的同时不大规模的引入问题。为此,你需要学习《Mikado Method [BE12]》。本书也帮你了解这些支撑性实践。
简单地为已经完成的代码添加单元测试(有时我称为开发后测试)常常没什么效果,特别是你与”系统就是这样工作的”搏斗时。你可能投入了数千小时的私人时间来写测试,但系统质量并没有明显的改进。
如果你允许TDD帮助你改造系统,你的设计肯定是可测试的。你的设计会完全不同,许多方面与不使用TDD相比,会变得更好。关于好设计是什么样的,你知道的越多,TDD越能帮助你达到好的设计。
为了协助你转向考虑设计,本书强调好代码的原则,比如面向对象设计的SOLID原则,《Agile Software Development, Principles, Patterns, and Practices》中对此进行了阐述。我会讨论这些好的设计如何帮助开发过程和开发效率,以及TDD如何帮助开发人员达成一致和可靠的成果。
本书为帮助的所有水平的C++程序员而写, 从只有基本语言知识的新手,到浸泡过语言秘籍的老手。如果你远离C++有一段时间了,你会发现TDD的快速反馈过程帮助你快速重新赶上。
虽然本书的目标是帮助你学习TDD,但是即使你没有TDD经验也能发现本书的价值。如果你是一个完全没概念的单元测试新手,我会带你一步步了解TDD的基础。如果你只是对TDD陌生,你会在书中发现有价值的专家意见都以简单的方式出现,并辅助直观的例子。即便是熟练的测试驱动开发者也会发现很多明智的建议,实践的理论基础知识和新的扩展主题。
如果你迟疑,你可以从不同角度探索TDD。关于为什么TDD可以工作,我掺杂了自己的理解;关于什么时候TDD不能很好的工作,以及为什么,我共享了一些经验。本书不是一个宣传手册,只是让你眼界大开的探索了一项革新技术。
各层次的读者都会注意到在团队中推广和保持TDD的思路。开始转向TDD很容易,但你的团队可能在过程中遇到挑战。如何防止这些挑战毁了你的转向工作。如防止这些问题?在推广和保持TDD这章,我展现了一些我见证过的思路。
编码本书的任何例子,你需要一台电脑(当然)和一个单元测试工具。有些例子需要第三方类库。本节概述了这三个因素。你也可以参考全局配置这章,以获取你所需内容的进一步信息。
在数十个可能的C++单元测试工具中,我选择了Google Mock(它是基于Google Test的)用于大多数本书中的例子。当前,它是网络搜索中最流行的,但是我选择它主要是因为它支持Hamcrest记法(一个匹配式的断言,用于提高测试代码的表述能力)全局配置中的信息可能帮助你加速对Google Mock的了解。
但是,本书即不是一个Google Mock的可读说明书,也不一个宣传手册。它实际是一本帮助掌握TDD原理的书。关于Google Mock,你只会在本书学到足够有效实践TDD的知识。
你也会使用另一个单元测试工具,名叫CppUTest,用于其它一些例子。你会发现很容易学习另一个单元测试工具,这也许可以让你放松一点,如果你担心你没用过Google Mock或CppUTest。
如果你正在使用不同的单元测试工具,例如CppUnit或Boost.Test,不用担心。这些工具的概念类似于Google Mock,实现也十分相似。无论用任何C++单元测试,你可以轻松跟上并完成TDD例子。单元测试工具比较这章讨论了选择的单元测试工具的要点。
本书的很多例子使用Google Mock来模仿和打桩(参考双面测试)。显然,Google Mock和Google Test可以一起工作,但是你也可以把Google Mock成功集成到你选中的单元测试工具中。
你需要使用一个支持C++11的C++编译器。本书的例子代码最初使用gcc构建,并在Linux和Mac OS上测试成功。参考全局配置这章以取得在windows上构建代码的信息。所有的例子都使用STL,这是所有平台上现代C++开发的重要部分。
一些例子使用了自由获取的第三方类库。参考全局配置取得你需要下载的类库列表。
我设计本书的章节可以尽可能的独立阅读。你可以随机选择一个章节,读完它一般不需要完全阅读任何其它章节。我提供了大量交叉引用,以方便你使用电子阅读器时可以来回跳转。
每章以一个简单的概述开始,并以一个小结加下章预告结束。我选择Setup和Teardown做为这些简述段落的标题,有趣地对应着许多单元测试框架中初始化和结束段落。
本书包括大量代码例子。大部分代码出现时引用了文件名。你可以在下面的网址找到本书例子的完整代码,或者也可以在我的GitHub主页找到。
在代码的发布包中,例子按章节分组。除了每章的文件夹,你还可以看到数字文件夹, 每个数字有效指明了版本号(这让本书显示每章进展过程中例子代码的变更)。例如,代码标题为c2/7/SoundexTest.cpp指明文件SoundexTest.cpp位于第2章(c2)的第七个版本文件夹中。
请加入讨论组。论坛的目的是讨论本书,也包括使用C++进行TDD的一般问题。我会发表一些本书相关的有用信息。
虽然本书面向的所有人,但本书主要聚焦于新的TDD程序员,因此本书的章节是一个适合的顺序。我高度推荐你完成《Test-Driven Development: A First Example》中的练习。它会给你一个深刻的印象,你在完成练习中会了解许多TDD背后的思想。不要只是读,输入,当需要通过时,保证你能通过测试。
接下来两章,测试驱动开发基础和测试构造,都是必读。它们覆盖了什么是TDD,以及什么不是TDD的核心观点,以及如何构造测试。在学习桩测试之前,确认你对这两章的内容已经熟悉。桩测试(双面测试这章谈到)是一种构造大多数产品系统的必备技术。
不要跳过设计和重构章节(增量设计),即使你认为你已经知道这是什么。实践TDD必须的原因之一是使你能逐步演进设计,并通过持续重构保持代码清晰。大多数系统显示出设计馈乏和编码艰难,部分因为开发人员不愿充分重构或者不知道如何做。你会学到足够方法,如何从一个小的,简单的系统开始,取得可能的收益。
做为TDD核心技术的延伸,质量测试审视了许多提升TDD回报率的方法。学习这些技术可以让TDD的初步和良好状态完全不同。
你确实承担着一个不是测试驱动的既有系统的责任。你可读遗留的挑战这章,从一些简单技术开始,去处理遗留代码。
在遗留代码相关材料之后,你会遇到一个专注于测试驱动开发多线程的章节。测试驱动开发多线程程序的方式会让你大吃一惊。
下一章,增加了一些TDD的概念和相关讨论,对特定领域和关心的问题进行了深入讨论。你会发现一些TDD的最近思想,包括与本书中其它部分不一样的代替方法。
最后,你会想知道TDD会为你的团队带来什么,你当然也会想确认你能保持你在TDD方面的投资。
最后一章,推广和保持TDD,提供了一些你用的着的思路。
你完全可以随机选择一些章节。你也能找到不少很难以学到的窍门,不过,这些窍门散布在本书中。
任何规模的代码段都会与文本分开。当正文引用到代码内容时,使用以下约定:
为了保持简洁或不要浪费纸张,代码清单常常略去与当前讨论不相关的部分。注释后跟上省略号代表略去的代码。例如,for循环的内容被替换了,代码片段如下:
for (int i = 0; i < count; i++) { // … }
我按我们之间的对话来写这本书。通常我在对你(读者)说话。当我(应该不常见)指我自己时,通常是在表述基于经验的意见或偏好。这暗示那些内容可能未被广泛的接受,但是它也可能是一个好想法。
当进入编码时,我和你都不应该单干,特别当你准备学习编码的时候。我们会一起完成全书所有的编码练习。
我从1980就开始编程,我早期在高中编程,1982年开始专业编程(我为马里兰州大学工作,当时我在完成计算机科学学士)。2000年,我从程序员转变为顾问,当时我快乐地与Bob Martin以及其它Object Mentor公司的伟大人物一起工作。
我于2003年开始Langr软件解决方案(公司),提供敏捷软件开发相关的顾问和培训服务。大多数我的工作是与开发人员一起结对进行TDD或者教学。我坚持在顾问/培训和真正的程序员之间切换,坚持工作在真实的开发团队中,以保持自己“最新”和“最近”。从2002年开始,我做为一个全职程序员分别在四个不同的公司工作过足够长的时间。
我喜欢写软件开发的文章。这是我如何深入学习的一部分,但是我也喜欢帮助别人掌握高质量构建代码。这是我的第四本书。我编写了 Essential Java Style: Patterns for Implementation [Lan99] ,Agile Java: Crafting Code With Test-Driven Development [Lan05], 与 Tim Ottinger 一直合著了Agile in a Flash [OL11]。我也为Uncle Bob的 Clean Code: A Handbook of Agile Software Craftsmanship [Mar08]贡献了数章内容。我在自己以及其它网站上发布了上百篇文章。我定期写博客(http://langrsoft.com/jeff),在Agile in a Flash项目上,我参与或编写了上百篇博客(http://agileinaflash.com)
除了C++之外,我还使用其它几种语言编程:Java, Smalltalk, C, C#, 以及Pascal,另加一种其它需要保证低调的语言。我现在在学习Erlang,也可以挣扎着用Python和Ruby编码。我还玩过最少一打左右的其它语言,为的是看看它们是什么样,或者说支持一些短期工作。
虽然我有各种规模C++程序的经验,从很小到非常大,但是我还不认为自己是语言专家。我读过Meyers和Sutter的重要著作,以及其它相关著作。我知道如何让C++为我工作,如何让最后的代码有可读性和可维护性。我了解绝大多数语言的黑暗角落,但是准备避免使用它们的方案。本书中我关于“聪明”的定义是“难以维护”,我会引导你向一个更好的方向。
我的C++风格非常面向对象(毫无疑问源于大量使用Smalltalk, Java和C#编程)。我喜欢让代码在类的范围内结束。本书大多数的例子代码满足这个风格。例如,第一个例子中Soundex的代码就由一个类完成,虽然它并不必须的(参见Test-Driven Development: A First Example)。我喜欢这种方式,但是如果它突破了你的底线,可以按你的方式。
无论C++的风格如何,TDD都很有价值,因此不必让我的风格改变你可能的风格。但是,一个强OO风格使引入双面测试更容易,因为你需要打破有问题的依赖。如果你沉浸在TDD中,你很可能发现你的风格向这个方向慢慢转变。这不是坏事。
我有点懒。当相对小范围的例子中,我选择最小化使用命名空间,便是我当然不会把它用在任何真实产品的代码工作中。
我喜欢让代码尽可能的保持线性,避免有些我认为是视觉分散的问题。在大多数实现文件中,你会发现using namespace std;就是这个原因,虽然多数是不好的形式。(保持类或函数的小而专注让这个和其它指导意见,像”所有函数必须只有一个返回“没什么意义)。不用担心,TDD不会阻止你坚持你自己的标准,我也不会。
最后一句关于C++的话:它是一个巨大的语言。我确定肯定有更好的方式去编码书中的例子,我打赌有类库我没有用好。TDD的好处就是你可以用数十种方式重新实现,而不会破坏任何东西。不管怎样,请你发给我改进建议,但是必须是以测试驱动的方式!
感谢我的编辑 Michael Swaine ,和PraProg的朋友们的建议和支持。
感谢 Uncle Bob 生动的序言。
非常感谢技术编辑 Dale Stewart ,他在整个过程中提供的协助,特别是对本书中C++代码的反馈和帮助。
写作过程中,我总是要求近忽野蛮的诚实反馈,Bas Vodde 完全满足了我,为我提供了关于全书极多的反馈。他是隐形的结对伙伴,我需要他来保持诚的沟通。
非常感谢所有提供思路和无价的反馈的朋友们:Steve Andrews, Kevin Brothaler, Marshall Clow, Chris Freeman, George Dinwiddie, James Grenning, Michael Hill, Jeff Hoffman, Ron Jeffries, Neil Johnson, Chisun Joung, Dale Keener, Bob Koss, Robert C. Martin, Paul Nelson, Ken Oden, Tim Ottinger, Dave Rooney, Tan Yeong Sheng, Peter Sommerlad, 和Zhanyong Wan。如果我遗漏了任何人,我表示歉意。
感谢在PragProg的勘误页上提供反馈意见的朋友们:Bradford Baker, Jim Barnett, Travis Beatty, Kevin Brown, Brett DiFrischia, Jared Grubb, David Pol, Bo Rydberg, Jon Seidel, Marton Suranyi, Curtis Zimmerman, 以及其他人。
再次感谢Tim Ottinger, 他提供了介绍这章中的一些段落,同时为全书提供出一些思路。我希望能与你再次合作。
感谢所有使本书更好的人,我自己一个人无法做到这么好。
本书献给支持我做我喜欢的事的人们,特别是我的妻子,Kathy。