华章程序员书库
点击查看第二章
点击查看第三章
C++代码整洁之道:C++17 可持续软件开发模式实践
Clean C++: Sustainable Software Development Patterns and Best Practices with C++ 17
[德]斯蒂芬·罗斯(Stephan Roth)著
连少华 郭发阳 陈涛 译
第1章
简介
如何去做和做到同样重要。
—Eduardo Namur
目前大部分软件项目的开发形势依然很严峻,甚至有些项目处于严重的危机之中。其原因是多种多样的,有的项目是由项目管理的糟糕导致的,有的项目则是由于开发过程中需求的不断变化,而开发方式不能适应导致的。
在某些项目中,导致这一结果的只是单纯的技术原因,即代码质量不高。但这并不意味着代码的功能没有实现,相反代码的外部质量看似很高,能够通过质量保证部门的黑盒测试、用户测试以及验收测试。代码能够完美地通过QA,并且在测试报告上找不到任何错误。软件用户对软件也很满意,甚至软件能够按时交付并且不超出预算(当然,这种情况极少)。一切看起来很美好,但事实真的如此吗?
真相是这份能够正常工作的代码的内部质量实际上很低。通常代码可读性不高,维护和扩展困难。软件的组成单元,如类、函数非常臃肿,有的甚至会达到上千行的代码量。太复杂的依赖关系导致的结果就是,改变其中某一小部分内容所造成的影响将是难以预估的。软件的架构不具有前瞻性,其结构可能是由开发人员的临时“拍脑袋”决定的,也就是一些开发人员常说的“历史衍生软件”或者“随意的架构”。类、函数、变量、常量命名不规范,含义不明确,并且代码被大量无用的注释包围,有些注释已经过时了,有些注释只描述了显而易见的东西,甚至是完全错误的。不少开发人员害怕修改或扩展软件,因为他们知道自己的软件很脆弱,单元测试覆盖率很低甚至没有单元测试。在这样的项目中,“不要碰已经能够运行的系统”的声音不绝于耳。一个新的特性从开发到部署上线,通常不是几天就能完成的,这需要几周甚至几个月的时间才能完成。
这种糟糕的软件通常被称作“a Big Ball Of Mud”。这个术语由Brian Foote和Joseph W. Yoder在第四次模式编程语言会议上的一篇论文中第一次提到。Foote和Yoder将其解释为“……结构随意的、笨拙的、草率的、盘根错节的代码杂糅在一起”。这种软件系统维护起来是一个噩梦,不仅代价高昂还会花费大量时间,它通常会拖垮整个开发团队。
上述现象在整个编程领域中都客观存在着,与使用哪种编程语言没有关系,不管使用的是 Java、PHP、C、C#、C++,还是其他任何语言,都有可能产生“a Big Ball Of Mud”。那么,产生这一问题的根源是什么呢?
1.1 软件熵
首先,有些东西就好像是一种自然规律,就像其他一些封闭和复杂的系统,软件会随着时间的推移而变得混乱,这种现象称为软件熵。这个词借鉴了热力学第二定律,意思是指,一个封闭系统的总混乱度不会减小,只能保持不变或增加。软件的表现看起来就是这样的,每次添加一个新功能或者改变一些原有功能,代码都会变得更加混乱,有很多影响因素能够提高软件熵,举例如下:
□不切实际的项目进度安排会给程序员增加压力,进而迫使开发人员以一种糟糕和非专业的方式处理开发工作。
□当今,软件系统大都庞大而复杂。
□开发人员拥有不同的技能水平和开发经验。
□全球分布的、跨文化差异的团队,执行和交流方面存在的问题。
□开发人员主要关注软件的功能性方面(功能性的需求和系统的用例),以致质量要求(例如非功能性要求),如性能、可维护性、可用性、可移植性、安全性等被忽略甚至被完全忘记了。
□不当的开发环境和糟糕的开发工具。
□管理层专注于眼前利益,而不了解可持续软件开发的价值所在。
□快速而糟糕的程序开发以及软件设计与实现的不一致(例如破窗理论)。
破窗理论
破窗理论是在美式犯罪研究中发展起来的。该理论指出,一幢被遗弃的建筑物中的一个被破坏的窗户,可能是整个周边地区开始破败的一个触发器。破碎的窗户给环境发出了致命的信号:“看,没人在乎这幢大楼!”,这引起了进一步的腐化、破坏和其他反社会行为。破窗理论一直是刑事政策学的很多改革的基础,特别是发展出零容忍策略。
在软件开发中,该理论被采用并应用于代码质量。对程序不适当的开发和糟糕的实现称为“破窗”。如果这些不好的实现没有被修复,那么会有更多不适当的代码出现在它们周围,因此,代码的混乱就开始了。
不要容忍“破窗”出现在你的代码中—及时地改正它们。
然而,似乎C和C++项目特别容易出现混乱,而且比其他编程语言更容易陷入一种糟糕的状态。即使是在互联网上,同样也充斥着大量的、糟糕的C++代码的例子,它们看起来快速且高度优化,但实际上是使用了高技巧的语法,并且完全忽略了设计良好和易维护的代码的基本准则。
导致本节问题的原因之一可能在于C++是一个中等层次的多范型编程语言,即它包含了高级和低级语言的特点。使用C++编程语言,你可以编写庞大且复杂的用户界面的分布式业务软件系统,也可以编写小型的嵌入式实时响应系统,这要求与底层硬件密切关联。多范型编程语言意味着你能够编写程式化、功能化或面向对象的程序,甚至三种范型的混合体。此外,C++支持模板元编程(Template Metaprogramming,TMP),这种技术用到了一种被称作模板的东西,模板被编译器用于生成临时使用的源代码,而这些临时的源代码会和其余的源代码合并在一起,并进行编译。自从ISO发布了支持C++11标准以来,更多的方法被加入到C++中,例如,具有匿名函数的函数式编程现在能通过lambda表达式以一种非常优雅的方式完成。由于这些多样性能力,C++同时也具有非常晦涩难懂、复杂和烦琐的名声。
开发出糟糕软件的另一个原因可能是很多程序开发者并没有IT背景。如今,任何人都可以开始开发软件,不必管他是否获得了大学学位或者是否是任何其他计算机科学方面的人才。绝大多数C++开发者都是(或曾是)非专业人员,特别是在汽车、铁路运输、航空航天、电气/电子或机械工程等技术领域。许多开发工程师在投入编程之前的几十年里并没有受到过计算机科学方面的教育,随着复杂度的增加以及技术系统包含了越来越多的软件,世界对程序员的需求很迫切,这种需求被现存的其他劳动力所补充了,电气工程师、数学家、物理学家,还有很多人严格来讲并非是经专业训练过而开始开发软件的,他们主要通过自学和实操而简单地进行开发,并且他们已经尽了最大的努力。
基本上来看,这绝对是无可厚非的,但有时只知道开发工具和编程语言是不够的!软件开发与编程不一样,世界上很多的软件是由没有经过培训的软件开发人员在一起开发和维护的,开发人员要在抽象层次考虑很多的事情,以便创建一个可持续的系统,例如架构和设计。如何构建高质量达到某些目标的系统?面向对象的东西有什么好处?我如何有效地使用它呢?某个框架或库的优点和缺点是什么?各种算法之间的差异是什么?为什么同一个算法不适合所有类似的问题?到底什么是有限状态机,为什么它有助于处理复杂性问题?
不要灰心!一个软件的持续健康需要有人去关注它,而整洁的代码就是处理的关键所在!
1.2 整洁的代码
人们常常混淆整洁的代码和“漂亮的代码”。其实,整洁的代码中并不包含漂亮的因素。专业的程序员通常不会写漂亮的代码,因为,他们是以专家的身份而工作,并为客户创造价值的。
整洁的代码是容易被任何团队的成员理解和维护的。
整洁的代码是高效工作的基础。如果你的代码是整洁的并且测试覆盖率也比较高,增加一个函数或修改一部分代码,只会花费几个小时或几天的时间,否则可能需要几周或数月的时间才能完成开发、测试和部署。
整洁的代码是软件持续发展的基础,能够在没有欠下大量技术债务的情况下保证软件运行很长的一段时间。开发人员必须积极主动地维护代码并保证代码保持一定的风格,因为代码是软件开发公司生存的根本。
整洁的代码也是一个让你成为一个快乐的开发者的主要因素。它能够让你毫无压力地生活。如果你的代码是整洁的并且自我感觉良好,即使今天是项目的最后期限,你也可以处变不惊。
上面提到的几点都是实际存在的,最关键的一点是:整洁的代码能够节省金钱!本质上来讲,这和经济效率有关,每年,软件开发公司都会因为糟糕的代码质量而损失大量的金钱。
1.3 为什么使用C++
C可以让你很容易地搬起石头砸自己的脚,C++则困难得多,但当砸到的时候,你就会失去整条腿!
—Bjarne Stroustrup, Bjarne Stroustrup's FAQ:你是认真的吗?
每种编程语言都是一种工具,并且,每种编程语言都有自己的优点和缺点。软件架构师的一个重要工作就是选择一种编程语言(或是多种编程语言),让合适的语言在项目中做合适的事情。这是一项不能受个人喜好左右的重要决策。类似的,“在公司我们根据《replace this with the language of your choice》做任何事”不是一个好的指导原则。
作为一种多范型程序设计语言,C++把不同的思想和概念融合到了一起。在操作系统、设备驱动程序、嵌入式系统、数据库管理系统、计算机游戏、3D动画和计算机辅助设计、实时音频和视频处理、大数据管理系统和很多其他高性能的应用中,C++语言一直都是一个很好的选择。在某些领域中,C++可以与其他语言混合使用。数十亿行的大量的C++代码目前仍在使用中。
几年前,人们广泛地认为C++很难学以致用。对于那些经常肩负编写大型复杂程序任务的程序员来说,这种语言可能是复杂而令人畏惧的。鉴于此观点,解释型编程语言和托管型编程语言,如Java或C#变得流行起来。由于这些语言厂家的过度营销,解释型语言和托管型编程语言在某些领域占据了主导地位,但是在其他领域,编译型语言仍然占据主导地位。编程语言并不是宗教。如果你不需要C++的高性能特性,比如Java可以让你更轻松的工作,那么你就可以使用Java而不是C++。
1.4 C++11—新时代的开始
有人说C++目前正处于伟大的复兴时期,有些人甚至说这是一场革命。现在的C++已经不是20世纪90年代早期的C++了,这种趋势的催化剂主要是在2011年9月出现的C++标准ISO/IEC 1488∶2011 [ ISO11],称为C++ 11标准。
毫无疑问,C++11带来了一些伟大的创新。在本书的编写过程中,C++标准委员会完成了C++17标准的起草工作,这一标准已提交ISO国际标准化组织处理。与此同时,C++20也已经完成了开始部分的编写。
目前,在传统行业发生了很多事情,尤其是在公司和制造业上,因为作为技术系统的软件已成为最重要的增值因素。C++开发工具变得更加强大了,并且也出现了很多可用的第三方库和框架。但我并不认为这是一场革命,我认为这是一种演进。同时,编程语言必须不断改进以满足新的需求。C++98和C++03是两个标准,C++03主要是在C++98的基础上修复了一些bug。
1.5 适合本书的读者
作为一名培训讲师和顾问,我有机会去很多正在开发软件的公司,同时,我也非常仔细地观察了在开发过程中正在发生的一些事情,并且我也已经意识到了C++阵营与其他开发语言阵营的差距。
给我的印象是,C++程序员已经被那些促进软件工艺和整洁代码开发的人员忽视了。相对来说,在Java环境中,以及在Web或游戏开发世界中,许多熟知的原则和实践在C++开发领域似乎都不被人所知道。一些开创性的书籍,如Andrew Hunt和David Thomas的《Pragmatic Programmer》[ hunt99 ],或是Robert C. Martin的《Clean Code》[ martin09]也同样如此。
而本书试图缩小这种差距,因为即使是C++,代码一样可以写得很整洁!如果你想让自己写的代码更加整洁,那么本书适合你阅读。
本书不是C++的入门书!你应该已经熟悉了C++语言的基本概念,才能有效掌握本书的内容。如果你只是想从C++开发开始,并且没有C++语言的基础知识,你应该首先通过其他书籍(如《C++ Primer》)学习,或选择一个好的C++入门的练习项目。
此外,本书也不包含任何深奥的技巧和杂乱的知识点。我知道C++有很多令人兴奋的技巧,但这些通常不是整洁代码的精神,也不是现代C++的代码风格。如果你真的沉迷于C++的裸指针,那么本书不适合你阅读。
本书中的一些代码示例,用到了C++11标准(ISO/IEC 14882:2011)和C++14标准(ISO/IEC 14882:2014)的多个特性,也用到了一些C++17标准的特性。如果你不熟悉这些特性,也不用担心。我将通过扩展阅读的形式,提供一些简要的介绍。需要注意的是,实际上目前并不是所有的C++编译器都支持C++语言的所有新特性。
除此之外,本书为了帮助C++程序员提高技能水平,举例说明了如何编写易于理解的、灵活的、可维护的和高效的C++代码。即使你是一个经验丰富的C++开发人员,本书中也有一些值得你学习的地方,我认为这些值得学习的地方能够促进你的工作。书中所提出的原则和实践可以应用于新的软件系统,有时被称为“绿地项目”;以及具有悠久历史的遗留系统,通常被称为“棕地项目”。
1.6 本书使用的约定
本书使用的排版约定如下所示:
楷体字:新的术语、名字,或扩展内容。
黑体字:段落中强调的术语或重要的语句。
等宽字:段落中表示程序元素,如类、变量或函数名,语句或C++关键字。这种字体还表示命令行的输入参数,一个按键序列或者程序的输出结果。
1.6.1 扩展阅读
有时候我想告诉你一些与上下文毫不相关的信息,它们是相对独立的内容。这些部分就叫作扩展阅读,用于对其附近的主题做进一步讨论或对比论证。例子:
此处为扩展阅读的标题
此处为扩展阅读的内容。
1.6.2 说明、提示和警告
还有另一种形式的扩展阅读,提供说明、提示和警告的相关信息。它们的目的是为你提供一些特殊的信息,一条建议或者警告你一些比较危险需要注意避免的事情。例子:
说明 此处为说明的内容
1.6.3 示例代码
示例代码和代码片段与正文相互独立,使用等宽字体且语法高亮(C++关键字为黑体)。较长的代码片段通常含有标题。为了快速地指明代码所在位置,代码示例中通常会给出行号。
为了更好地关注代码中特定的部分,不相关部分的代码将被隐藏并用一个注释替换,其形式为“//…”,参考下方的示例代码:
1.6.4 编码风格
简单说一下本书使用的代码风格。
你可能会发现我的编码风格和典型的Java风格非常相似,混有Kernighan and Ritchie (K&R)风格。在我近20年的开发生涯中,我学习的编程语言远不止C++,还包含ANSI-C、Java、Delphi、Scala以及其他脚本语言,今后亦是如此。因此,我在学习过程中不断吸取各方之长,已经形成了一套自己的编码风格。
也许你不喜欢我的编码风格,并且更倾向于Linus Torvald 的内核编码风格,Allman风格或其他流行的C++编码风格。我想说的是,每个人都有自己的编码风格,不必要求个人的编码风格完全一样。
1.7 相关网站和代码库
本书附有一个网站:www.clean-cpp.com。
该网站包含了以下内容:
□一个论坛,读者可以与其他读者讨论特定主题,当然也可以与作者讨论。
□一些本书可能尚未涉及的附加主题的讨论。
□本书中所有图解的高分辨率版本。
本书中的大多数源代码示例以及其他有用的附属物都可以在GitHub上获得,网址为:https://github.com/clean-cpp。
你可以使用Git的以下命令导出代码:
你还可以去 https://github.com/clean-cpp/book-samples网站上点击“Download ZIP”按钮下载一个“.zip”格式的文件。
1.8 UML图
本书中的一些插图是UML图。统一建模语言(UML)是一种标准化的图形语言,用于创建软件和其他系统的模型。在当前的2.5版本中,UML提供了14种图表类型来全面描述系统。
如果你不熟悉所有的图表类型,请不要担心,我在本书中只使用其中的一部分。有时我提出UML图是为了提供某些问题的概述,而这些问题可能无法通过阅读代码快速检测到。在附录A中,简单介绍了UML使用的符号。