好代码 ,坏代码

978-7-115-59641-3
作者: [英]汤姆·朗(Tom Long)
译者: 姚军茹蓓
编辑: 秦健

图书目录:

详情

本书分享的实用技巧可以帮助你编写鲁棒、可靠且易于团队成员理解和适应不断变化需求的代码。内容涉及如何像高效的软件工程师一样思考代码,如何编写读起来像一个结构良好的句子的函数,如何确保代码可靠且无错误,如何进行有效的单元测试,如何识别可能导致问题的代码并对其进行改进,如何编写可重用并适应新需求的代码,如何提高读者的中长期生产力,同时还介绍了如何节省开发人员及团队的宝贵时间,等等。

图书摘要

版权信息

书名:好代码 ,坏代码

ISBN:978-7-115-59641-3

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [英] 汤姆·朗(Tom Long)

译    姚 军  茹 蓓

责任编辑 秦 健

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e59641”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


Original English language edition, entitled Good Code, Bad Code: Think like a Software Engineer by Tom Long published by Manning Publications, USA. Copyright © 2021 by Manning Publications.

Simplified Chinese-language edition copyright © 2022 by Posts & Telecom Press, Co. LTD. All rights reserved.

本书中文简体字版由Manning Publications授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


本书分享的实用技巧可以帮助你编写鲁棒、可靠且易于团队成员理解和适应不断变化需求的代码。内容涉及如何像高效的软件工程师一样思考代码,如何编写读起来像一个结构良好的句子的函数,如何确保代码可靠且无错误,如何进行有效的单元测试,如何识别可能导致问题的代码并对其进行改进,如何编写可重用并适应新需求的代码,如何提高读者的中长期生产力,同时还介绍了如何节省开发人员及团队的宝贵时间,等等。

本书文字简洁、论述精辟、层次清晰,适合零基础及拥有一定编程基础的开发人员阅读,对于高等院校计算机及相关专业的学生,也具有很高的参考价值。


从11岁开始,我一直以这样或那样的方式编程,因此,到我找到第一份工作并成为一名软件工程师时,我已经写了很多代码。尽管如此,我很快发现编程和软件工程不是一回事。像软件工程师那样编程,意味着我的代码必须对其他人有意义,并且在他们做出更改时不会崩溃。这还意味着,真的有人(有时候还是很多人)使用和依赖我的代码,因此出现错误的后果比以前严重多了。

软件工程师在不断积累经验的过程中会发现,日常编程中所做出的决策对于软件的正常运行、工作的顺利开展以及其他人的维护有很大的影响。学习编写(从软件工程角度来看)优良代码需要花费许多年的时间。这些技能的获得过程往往很缓慢。工程师从自己的错误中吸取教训,或者不断从团队的资深工程师那里得到建议,以特定的方式得到这些技能。

本书旨在帮助刚入门的软件工程师获取这些技能。它将传授一些非常重要的经验教训和基础理论,帮助读者编写可靠的、易于维护且能够适应不断变化需求的代码。


本书介绍软件工程师经常用于编写可靠的、易于维护的代码的关键概念与技术。本书并不是简单列举“该做”和“不该做”的事项,而是旨在解释每种概念和技术背后的核心理论,以及需要权衡的因素。这应该能够帮助读者对如何像一位经验丰富的软件工程师那样思考和编程有基本的理解。

本书的目标读者是那些已经具备基本编程技能,想继续提高编程技能的人。本书适合有0~3年软件工程师工作经验的人阅读。有丰富工作经验的工程师可能发现,本书中的许多内容他们都已经掌握,但我希望他们把这本书当作指导其他同行的有用资源。

本书分为三部分,共11章。第一部分介绍较为理论性的概念,它们组成了我们对代码的思考方法。第二部分转向较为实用的经验教训。第二部分的每一章都分为一系列主题,分别涵盖特定的考虑因素或技术。第三部分介绍创建有效和可维护的单元测试的原则与方法。

本书各章节的总体形式是:先阐述一个可能有问题的场景(以及部分代码),然后说明消除部分或全部问题的替代方法。从这个意义上说,每个章节往往是从展示“坏”代码过渡到“好”代码,但需要注意的是,是主观的说法,与语境相关。正如本书所要强调的,在编程工作中往往要考虑一些微妙的差别和权衡,这也就意味着好坏的区别并不总是一目了然。

第一部分“理论”为一些总体性和较为理论性的考虑因素打下基础。这些考虑因素组成我们像软件工程师那样编写代码的方法。

第1章介绍代码质量的概念,特别是我们打算用高质量代码要实现的一组实际目标。然后,我们将这些目标展开为“代码质量的六大支柱”,为日常编程使用提供高层策略。

第2章讨论抽象层次。这是指导我们如何构造代码,并将其分解为不同部分的基本考虑因素。

第3章强调考虑必须使用我们的代码开展工作的其他工程师的重要性。本章还将讨论代码契约,以及如何仔细考虑这些契约以防止软件缺陷。

第4章讨论软件错误,并阐释为何认真思考错误通知与处理方法是编写优良代码的关键部分。

第二部分“实践”以更贴合实践的方式,用特定的技术与示例介绍代码质量的前五大支柱(第1章定义的)。

第5章介绍提高代码可读性的方法。这能确保其他工程师理解代码的意义。

第6章介绍避免意外情况的方法。这能确保其他工程师不会误解代码的功能,从而最大限度地降低出现缺陷的可能性。

第7章介绍使代码不容易被误用的方法。这使得工程师不容易在不经意间编写出逻辑错误或者违反假设的代码,最大限度地降低出现缺陷的可能性。

第8章介绍实现代码模块化的方法。这种关键技术有助于确保代码表现出清晰的抽象层次,能够适应不断变化的需求。

第9章介绍代码的重用性和可推广性。这能避免软件工程师重复编写类似代码,使得添加新功能或构建新特性更加方便、安全。

第三部分“单元测试”介绍编写高效单元测试的关键原则和实用方法。

第10章介绍影响单元测试代码的一些原则和考虑因素。

第11章以第10章介绍的原则为基础,为编写单元测试提供一系列具体、实用的建议。

阅读本书的理想方式是从头到尾完整阅读,因为本书前面的章节是后续章节的基础。尽管如此,第二部分(以及第11章)中的主题通常相对独立,而且每个主题篇幅较小,因此,即使单独阅读,也是有益处的。这样的编写方式是有意为之,目的是提供向其他工程师快速解释既定优秀实践的有效手段。对希望在代码评审中解释特定概念或指导其他工程师的工程师来说,这是非常有用的。

本书的目标读者是使用静态类型、面向对象编程语言,例如Java、C#、TypeScript、JavaScript(ECMAScript 2015或有静态类型检查器的更新版本)、C++、Swift、Kotlin、Dart 2或类似语言的工程师。在使用类似语言编程时,本书涵盖的概念有广泛的适用性。

不同编程语言表达逻辑与代码结构的语法与范式也不同。但为了在本书中提供代码示例,必须标准化某些语法和范式。为此,本书借鉴了不同编程语言的伪代码。使用伪代码的目的是为大多数工程师提供明确、清晰且易于理解的信息。请牢记这一实用的意图,本书的目的不是说明某种编程语言的优劣。

同样,当我们需要在无歧义与简洁之间做出权衡的时候,伪代码的例子倾向于无歧义这一方面。这方面的例子之一是使用明确的变量类型,而不是使用var之类关键字的推断类型。另一个例子是用if语句处理空值,而不是更简洁(但或许更不熟悉)的空值合并和空值条件运算符(参见附录B)。在真实的代码库中(本书的语境之外),工程师可能更希望强调简洁性。

在阅读任何关于软件工程的书籍或文章时,一定要记住这是主观的论题,并且对现实问题的解决方案通常不是完全明晰的。按照我的经验,优秀的工程师总是带着健康的怀疑心态去阅读任何文章,并渴望理解其中的基本思路。人们的观点各不相同且不断发展,同时可用的工具和编程语言也在不断改进。想要知道在何时运用特定建议、何时忽略它们,就必须理解它们的缘由、背景以及限制范围。

本书旨在收集一系列有用的主题和技术,以引导工程师写出更好的代码。尽管考虑这些主题和技术或许是明智之举,但不应该将其看成绝对正确的理论,或者将其作为绝不能破坏的硬性规则。良好的判断力是优秀工程师必不可少的特征。

本书旨在成为软件工程师进入编程世界的敲门砖。它应该能够帮助读者大致了解代码的相关思维方式、可能出现的问题以及避免这些问题的技术。但我们的旅程不应该止于此,软件工程是一个庞大且不断发展的领域,博览群书并跟上最新发展是可取的做法。除阅读文章和博客之外,下面这些相关书籍也可能对读者有帮助:

Refactoring: Improving the Design of Existing Code[1], second edition, Martin Fowler (Addison- Wesley, 2019);

[1] 本书中文版《重构:改善既有代码的设计(第2版)》已由人民邮电出版社引进出版,ISBN:978-7-115- 50865-2。

Clean Code: A Handbook of Agile Software Craftsmanship[2], Robert C. Martin (Prentice Hall, 2008);

[2] 本书中文版《代码整洁之道》已由人民邮电出版社引进出版,ISBN:978-7-115-21687-8。

Code Complete: A Practical Handbook of Software Construction, second edition, Steve McConnell (Microsoft Press, 2004);

The Pragmatic Programmer: Your Journey to Mastery, 20th anniversary, second edition, David Thomas and Andrew Hunt (Addison-Wesley, 2019);

Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1994);

Effective Java, third edition, Joshua Bloch (Addison-Wesley, 2017);

Unit Testing: Principles, Practices and Patterns, Vladimir Khorikov (Manning Publications, 2020)。

写书并非单打独斗,我要感谢每一位为本书的出版提供帮助的人。特别感谢我的开发编辑Toni Arritola在整个写作过程中的耐心指导,以及她对读者和高质量教学始终如一的关注。我还要感谢策划编辑Andrew Waldron从一开始就相信本书介绍的理念。他在出版过程中提出了许多宝贵意见。感谢我的技术开发编辑Michael Jensen在技术方面的洞察力,以及对全书各章节提供的建议。感谢我的技术校对Chris Villanueva仔细审查本书的代码和技术内容,并感谢他给出的出色建议。

我还要感谢所有的审稿人员——Amrah Umudlu、Chris Villanueva、David Racey、George Thomas、Giri Swaminathan、Harrison Maseko、Hawley Waldman、Heather Ward、Henry Lin、Jason Taylor、Jeff Neumann、Joe Ivans、Joshua Sandeman、Koushik Vikram、Marcel van den Brink、Sebastian Larsson、Sebastián Palma、Sruti S、Charlie Reams、Eugenio Marchiori、Jing Tang、Andrei Molchanov和Satyaki Upadhyay——在出版的各个阶段花时间阅读本书,并提供了准确、可付诸行动的反馈。对这些反馈的重要性和实用性如何夸大都不过分。

本书涉及的绝大多数概念是软件工程领域享有盛誉的理念与技术,因此最后我要向多年来所有对这些知识做出贡献、分享成果的人表示感谢。


Tom Long是Google公司的软件工程师。他是一位技术负责人,除处理其他事务之外,还经常为刚入行的软件工程师传授专业编程的优秀实践经验。


本书封面配图的标题为“Homme Zantiote”,意为“来自希腊扎金索斯岛的人”。这幅插图取自Jacques Grasset de Saint-Sauveur(1757—1810)1797年在法国出版的名为Costumes de Différents Pays的服饰图集。该书的每幅精美插图都是手工绘制并上色的。Grasset de Saint-Sauveur收录的服饰丰富多彩。这生动地提醒我们,200 年前世界上不同城市和地区之间的文化有多么大的差异。当时的不同民族相互隔绝,语言和腔调也各不相同。在城市的大街上或者乡村,仅从服装就很容易判断出他们生活在什么地方、从事什么职业、社会地位如何。

自那时起,我们的穿着方式已经改变了,当时不同地区的那种多样性已经逐渐消失。不同大陆的居民都很难分辨,更不用说不同的城市、地区或者国家了。或许,我们已经用文化多样性换取了更加多样性的个人生活——当然是更多样性、节奏更快的科技生活。

在很难区分不同计算机书籍的时代,Manning以Grasset de Saint-Sauveur的插图帮助人们回想起两个世纪前丰富的地区生活多样性,并用这个封面颂扬计算机行业积极创新的精神。


本书由异步社区出品,社区(https://www.epubit.com)为您提供相关资源和后续服务。

您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e59641”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入错误信息,单击“提交”按钮即可,如下图所示。本书的作者和编辑会对您提交的错误信息进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

扫描下方二维码,您将会在异步社区微信服务号中看到本书信息及相关的服务提示。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区投稿(直接访问www.epubit.com/contribute即可)。

如果您所在的学校、培训机构或企业想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接通过邮件发送给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

异步图书”是由异步社区编辑团队策划出版的精品IT图书的品牌,依托于人民邮电出版社几十年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、人工智能、测试、前端、网络技术等。

异步社区

微信服务号


软件工程领域关于如何写出优秀代码的建议和观点非常多。但生活没有那么简单,绝不只是尽可能多地吸取好的建议并严格遵守。由于不同来源的建议往往相互矛盾,我们怎么知道要听从哪个建议。更重要的是,软件工程并不是一门精确的科学,不可能将其提炼为一套绝对可靠的原则(无论我们如何努力)。每个项目都不一样,几乎总有一些因素需要权衡。

为了写出优良的代码,我们必须对手上的方案有合理的判断,并彻底想清楚特定方法的结果(好的和坏的)。为此,我们必须了解问题的根本:编写代码到底是为了实现什么目标?实现那些目标需要考虑哪些高层因素?本书第一部分将介绍理论方面的内容,以便读者可以为编写优良代码打下坚实基础。


本章主要内容如下:

代码质量的重要性;

高质量代码要实现的4个目标;

我们用于确保代码质量的6个高层策略;

从中长期来看,编写高质量代码实际上可以节约时间与精力。

在过去的一年里,你可能已经使用过几百,甚至几千款不同的软件。安装在计算机中的每一个程序、手机上的每一款 App 以及你乐于与之打交道的每台自助收银机——我们和软件的互动非常频繁。

我们甚至在无意中依赖着许许多多的软件。例如,我们信任银行,认为它们的表现良好的后端系统不会意外地将我们账户上的钱转账给其他人,或者突然告知我们有数百万美元的负债。

有时候,我们会遇到对用户友好的软件:它们的功能正是我们想要的,缺陷很少,也很容易使用。而在其他时候,我们遇到的是可怕的软件,它们充满缺陷,总是死机,而且不直观。

有些软件的重要性显然不及其他软件。例如手机的 App 有一个缺陷,这或许只是令人烦恼,但并不是世界末日。相反,银行后端系统的缺陷有可能毁掉很多人的生活。即便一些看似不关键的软件问题也可能毁掉业务。如果用户觉得一款软件令人厌恶或者难以使用,那么他们很有可能会选择其他软件。

代码的质量越高,就越可能制作出更可靠、更容易维护、缺陷更少的软件。提高代码质量的许多原则关心的不仅是在初期确保软件达到上述标准,而且在整个软件生命期中需求发展和新场景出现时一直保持这种状态。图1-1说明了代码质量对软件质量的影响。

图1-1 高质量代码最大限度地提高了软件的可靠性、可维护性和满足其需求的机会。低质量代码则正相反

优良的代码显然不是制作优秀软件的唯一要素,但是主要的要素之一。我们可能拥有世界上最好的产品和营销团队,部署了最好的平台,并以最好的框架来构建软件,但归根结底,一款软件所做的一切,都是因为有人写了一段代码才得以实现的。

孤立地看,工程师们每天编写代码时所做出的决策都很小,有时显得微不足道,但它们共同决定了软件的好坏。如果代码中包含了缺陷、配置错误或者无法恰当地处理错误情况,由此构建的软件就可能有许多缺陷,甚至无法正常工作。

本章将介绍高质量代码应该实现的4个目标。随后,这些目标将扩展为日常工作中确保写出高质量代码的 6 个高层策略。本书后续内容将通过以伪代码编写的实例来详细探索这些策略。

在我们深入说明代码质量之前,简短地讨论一下代码如何变成软件是很有必要的。如果你已经熟悉了软件开发和部署过程,那么可以跳到1.2节。如果你知道如何编写代码,但从未承担过软件工程任务,那么本节应该能为你提供一个很好的概要。

软件是由代码构成的,这一点显而易见,无须说明。但是,代码变成在外界运行的软件(在用户手中或者执行业务相关的任务)的过程就不那么明显了(除非你已经有软件工程师的工作经验)。

工程师编写的代码一般并不会立即成为一款在外界运行的软件。通常要经过各种各样的过程和检查,以确定这些代码能够完成应该承担的工作,而且不会造成任何破坏。这些过程和检查往往被称作软件开发和部署过程。

阅读本书并不需要详细了解这一过程,但至少了解其概况还是有好处的。首先,我们介绍几个术语。

代码库(codebase)——包含软件构建所需各部分代码的存储库。这通常由git、subversion、perforce等版本控制系统管理。

代码提交(submitting code)——有时称为“提交代码”(committing code)或“合并-拉取-请求”(merging a pull request),程序员通常更改代码库本地副本中的代码。一旦他们对更改感到满意,就会将其提交到主代码库。注意:在一些机构中,由指定的维护人员而非代码编写者将更改提交到代码库中。

代码评审(code review)——许多组织要求在代码提交到代码库之前,由另一名工程师进行评审。这有点像代码校对,“第二双眼睛”往往能发现代码编写者遗漏的问题。

提交前检查(pre-submit check)——有时候称为“合并前钩子”(pre-merge hook)、“合并前检查”或“提交前检查”,如果测试失败或者代码未编译,这些检查将阻止代码提交到代码库。

发行(release)——软件是从代码库的一个快照中构建的。经过各种质量保证检查,这款软件就可以向外界发行了。你常听到的“剪切发行版本”(cutting a release)这个术语指的就是从代码库中取得某个修订版本,并从中制作一个发行版本的过程。

生产(production)——当部署软件到一台服务器或者一个系统中(而不是运送到客户那里)时,这个术语是“在外界运行”的恰当说法。一旦软件发行并执行业务相关任务,我们就称其为“运行在生产环境”。

代码转化成可运行软件的过程有许多变种,但这些过程中的关键步骤通常如下。

(1)工程师更改代码库的本地副本。

(2)工程师修改满意之后将发送这些更改,以进行代码评审。

(3)其他工程师将评审代码,可能会提出修改建议。

(4)一旦作者和评审人员都感到满意,将提交代码到代码库。

(5)定期从代码库剪切发行版本。在不同组织和团队中,这一工作的频率也不一样(从几分钟到几个月不等)。

(6)任何测试失败或者代码未编译,都可能阻止代码提交到代码库,或者阻止其发行。

图1-2展示了典型软件开发与部署过程的概况。不同公司和团队都有各自的版本,各部分的自动化程度也各不相同。

值得注意的是,软件开发和部署过程本身就是庞大的主题,关于它们的专门著作很多。围绕这些过程也有许多不同的框架和思想方法,如果你对此感兴趣,也值得阅读更多的相关书籍。本书主要不是针对这些主题的,因此不会进一步详细介绍。对本书来说,你只需要大概了解代码是如何变成软件的就可以了。

图1-2 典型软件开发与部署过程的简化框图。在不同的组织和团队中,具体的步骤和自动化水平可能大不相同

如果我们购买一辆轿车,质量或许是我们考虑的主要因素之一。我们希望这辆轿车:

安全;

能正常使用;

不出故障;

表现可以预测——当我们踩下制动器,车子应该慢下来。

如果我们问某人,是什么造就了一辆高质量的汽车?最可能得到的答案就是“精良的制造”。这意味着优秀的设计,在投产前进行安全性和可靠性测试,并正确组装。软件也大抵如此:要制作出高质量的软件,就必须确保它“制造精良”。这就是代码质量的全部含义。

“代码质量”这个词有时可能会让人联想到,对一些不重要的琐事提出的近乎吹毛求疵的建议。毫无疑问,你时常会遇到这种建议,但它们并不是代码质量的真正含义。代码质量很大程度上出于对实际情况的考量,它有时与细节有关,有时又关乎大局,但目标都是一样的:创造更好的软件。

尽管如此,代码质量仍然是我们很难确切定义的一个概念。有时候,我们看到某些代码时可能会想“看起来很不舒服”,其他时候则可能偶然看到一段“看起来非常棒”的代码。代码为何引起这种反应,原因并不总是一目了然,有时候只是我们的本能反应,并没有确切的理由。

定义代码质量高低,本来就是主观的,更多的是出于判断。为了做出更客观的评判,我个人认为有益的做法是后退一步,考虑一下编写代码时真正试图实现的目标。在我看来,帮助我实现这些目标的代码就是高质量的,而产生阻碍作用的代码就是低质量的。

我在编写代码的时候要实现的4个高层目标如下:

代码应该正常工作;

代码应该持续正常工作;

代码应该适应不断变化的需求;

代码不应该重复别人做过的工作。

后面的内容将更加详细地介绍这4个目标。

这个目标显而易见,或许不需要说明,但我无论如何都要做一番解释。我们编写代码的目的是试图解决某个问题,例如实现某个功能、修复缺陷或执行某项任务。代码的主要目标是能够正常工作——它应该解决我们打算让它解决的问题。这意味着,代码是没有缺陷的,因为缺陷很可能阻止代码正常工作和全面解决问题。

确定代码“正常工作”的含义是,我们必须了解所有需求。例如,如果我们要解决的问题对性能(如延迟和CPU占用率)很敏感,确保代码有合适的性能就应该归入“正常工作”的范畴,因为这是需求的一部分。用户隐私和安全性等其他重要考虑因素也适用这一原则。

代码的工作可能非常短暂。今天,它可能正常工作,但我们如何确保明天或者一年之内它都能正常工作?这样的担心看起来好像莫名其妙,为什么代码会突然停止工作?要点在于,代码并不是与世隔绝的,如果我们不多加小心,它很容易因为周围事物的变化而崩溃。

代码很可能依赖于其他代码,而这些代码会被修改、更新和更换。

任何新功能需求都意味着要对代码进行修改。

我们试图解决的问题也可能随时间的推移而发展:消费者的偏好、业务需求和技术考虑都可能变化。

如果代码在今天能够正常工作,明天却因为上述因素变化而出现问题,那么它没有太大的用处。创建当下可以正常工作的代码往往很容易,但创建一直能正常工作的代码就要难得多。确保代码持续工作是软件工程师面对的问题之一,也是在编程各阶段都要考虑的问题。以事后诸葛亮的眼光去考虑它,或者认为只要以后增加一些测试就能实现这个目标,往往都不是很有效。

很少有只编写一次、永远不用再修改的代码。一款软件的持续开发可能跨越几个月、几年,甚至几十年。在整个过程中,需求都在改变:

业务状况变化;

消费者偏好变化;

设想失效;

新功能持续增加。

决定在代码适应性上投入多少精力,可能是很难权衡的问题。一方面,我们深知软件需求将随时间推移而发展(极少看到反例)。另一方面,我们往往不能确定它们究竟会如何发展。对一段代码或者一款软件,几乎不可能准确预测出它在以后的一段时间内将如何变化。然而,我们不能仅因为不能确定软件如何发展,就完全忽视软件会发展的事实。为了说明这一点,我们来考虑两种极端的情况。

方案 A——我们试图准确预测未来的需求可能会如何演变,并设计支持所有潜在变化的代码。我们可能要花几天或者几周的时间来描绘出代码和软件所有可能的演变路径。然后,我们必须小心翼翼地考虑缩写代码的每个细节,确保它支持所有未来可能出现的需求。这将严重拖慢我们的工作,本来3个月就可以完成的软件,现在可能要花上一年甚至更长的时间。最终,这些时间也可能是浪费的,因为竞争对手将比我们提前几个月进入市场,我们对未来的预测很可能完全是错误的。

方案 B——我们完全无视需求可能演变的事实。我们编写恰好满足现行需求的代码,不在代码适应性上做任何努力。软件中到处都是不可靠的假设,各个子问题的解决方案都捆绑在一起,成为一大堆无法区分的代码。我们在3个月内就投放了软件的第一个版本,但初始用户的反馈说明,如果我们想要取得成功,就必须改良其中的一些功能,并添加新功能。对需求的改变并不大,但因为我们编写代码时没有考虑适应性,唯一的选择就是扔掉全部代码,从头再来一遍。我们必须再花3个月重写软件,如果需求再次改变,此后还得再花3个月重写。到我们完成满足用户需求的软件时,竞争对手再一次打败了我们。

方案A和方案B是两个极端,两者的结果都很不好,它们也都不是制作软件的有效方法。相反,我们需要找到一种介于两个极端的方法。从方案A到方案B的整个谱系中,哪里才是最优的并没有唯一的答案,这取决于我们所开发的项目,以及我们所在单位的文化。

幸运的是,我们可以采用一些普适技术,在不确定未来变化的情况下确保代码的适应性。本书将介绍许多此类技术。

在我们编写代码解决问题时,通常会将一个大问题分解为多个较小的子问题。例如,我们打算编写加载图像文件,将其转换为灰阶图像,然后保存的代码,那么需要解决的子问题如下:

从文件中加载一些数据;

将这些数据解析为某种图像格式;

将图像转换为灰阶图像;

将图像转换为数据;

将数据存回文件。

这些问题中的许多已被其他人解决,例如,从文件中加载一些数据可能是由编程语言内置方法完成的。我们不用自己编写与文件系统进行低层通信的代码。同样,我们也许可以从现有的库中调用代码,将数据解析为图像。

如果我们自己编写与文件系统进行低层通信的代码,或者将一些数据解析为图像,实际上就是在重复别人做过的工作。最好的方式是利用现有解决方案而不是重写一遍。这样做的理由有多个方面。

节约时间和精力——如果我们利用编程语言内置方法加载文件,可能只要几行代码、花费几分钟。相反,自己编写代码完成这一工作可能需要阅读许多关于文件系统的标准文档,编写成千上万行代码。我们可能要几天甚至几周的时间才能完成这项工作。

降低出现程序缺陷的可能性——如果现有的代码能解决指定问题,它应该已经全面测试过。这些代码很有可能已在外界使用过,因此代码包含缺陷的可能性已经降低,即使有缺陷,人们可能已经发现并修复过。

利用现有专业知识——维护图像解析代码的团队很可能是由图像编程专业人士组成的。如果JPEG编程技术出现了新版本,他们很可能对此十分了解,并更新相应的代码。通过重用他们的代码,我们就可以从他们的专业能力和未来的更新中获益。

使代码更容易理解——如果完成某项工作有标准化的方法,我们就有理由认为,其他工程师此前也知道这种方法。大部工程师可能在某个时间阅读了一份文件,立刻意识到完成这项工作的(编程语言内置)方法,并理解这种方法的功能。如果我们编写自定义逻辑来完成工作,其他工程师就不会熟悉,不能一下子就知道它的功能。

“不应该重复别人做过的工作”这一概念在两个方向上都适用。如果其他工程师已编写了解决某个子问题的代码,那么我们应该调用这些代码,而不是自己编写代码来解决。同样,如果我们编写了解决一个子问题的代码,那么应该以某种方法构造代码,以便其他工程师能轻松地重用它们,而无须重复工作。

由于同一类子问题往往反复出现,因此人们很快就会意识到在不同工程师和团队之间共享代码的好处。

前面介绍的4个目标可以帮助我们聚焦要实现的根本目标,但它们并没有提供日常编程的具体建议。努力找出更加具体的策略,帮助我们编写符合这些目标的代码,是有益的。本书将围绕6个此类策略展开介绍。我将这6个策略称为“代码质量的六大支柱”(或许有些言过其实)。我们将首先概述每个支柱,后续的章节将提供具体的示例,说明如何在日常编程中应用它们。

代码质量的六大支柱如下:

编写易于理解(可读)的代码;

避免意外;

编写难以误用的代码;

编写模块化的代码;

编写可重用、可推广的代码;

编写可测试的代码并适当测试。

考虑如下这段文本。我们有意地使其变得难以理解,因此,不要浪费太多时间去解读。粗略地读一遍,尽可能吸收其中的内容。

取一个碗,我们现在称之为A。取一个平底锅,我们现在称之为B。在B中装满水,置于炉盘上。在A中放入黄油和巧克力,前者100g,后者185g。这应该是70%的黑巧克力。将A放在B之上;等待A的内容物融化,然后将A移到B之外。再取一个碗,我们现在称之为C。在C中放入鸡蛋、糖和香草香精,第一种原料放两个,第二种185g,第三种半茶匙。混合C的内容物。A的内容物冷却后,将其加入C中并混合。取一个碗,我们称之为D。在D中放入面粉、可可粉和盐,第一种原料50g,第二种原料35g,第三种半茶匙。完全混合D的内容物,然后过滤到C中。充分搅拌D的内容物使其完全混合。我们要用这种方法制作巧克力糕饼,我是不是忘记说这个了?在D中加入70g巧克力屑,充分搅拌D的内容物。取一个烘焙模具,我们称之为E。在E中涂上油脂并铺上烘焙纸。将D的内容物放入E中。我们将把你的烤炉称为F。顺便说一句,你应该将F预热到160℃。将E放入F中20min,然后取出。让E冷却几小时。

现在,我们提出一些问题。

这段文本说的是什么?

按照这些指示,我们最终能得到什么?

我们需要哪些配料?各种配料的分量是多少?

我们可以在这段文本中找到上述问题的答案,但并不容易。这段文本的可读性很差。造成这一结果的问题很多,包括如下。

没有标题,因此我们不得不通读整段文本,以领会它的意义。

这段文本没有很好地组成为一系列步骤(或者子问题),而是像一堵长长的文本墙。

用毫无益处的模糊名称指代事物,如“A”,而不是“装有融化后奶油和巧克力的碗”。

信息与需要它们的地方相隔甚远:成分与数量相互分离,烤炉需要预热这样的重要指示到最后才提及。

(你可能已经感到厌倦,没有读完这段文本,它是巧克力糕饼的食谱。如果你真想制作这种食物,附录A中有一个更易于理解的版本。)

阅读一段质量低劣的代码并试图领会其含义,与我们刚刚阅读巧克力糕饼食谱的体验没有什么不同。特别是,我们可能很难理解关于代码的如下情况:

做什么;

怎么做;

需要什么成分(输入或状态);

运行代码后得到什么。

在某个时间,其他工程师很有可能必须阅读并理解我们的代码。如果我们的代码在提交之前必须经过代码评审,那么这种情况几乎是立刻发生的。但即便忽略代码评审,在某个时间,其他人也会查看我们的代码,并试图领会它的作用。这可能发生在需求变化或者代码需要调试的时候。

如果我们的代码可读性很差,其他工程师就不得不花费很多时间来解读它。他们很有可能错误地理解它的作用,或者遗漏一些重要的细节。如果发生这种情况,代码评审期间就不太可能发现缺陷,而在其他人修改我们的代码、添加新功能时,就有可能引入新缺陷。软件的功能都是基于代码来完成的。如果工程师无法理解代码的作用,也就几乎不可能确定软件能否正常工作。正如食谱一样,代码必须易于理解。

在第2章中,我们将了解到,如何通过定义正确的抽象层次来帮助实现可读性。而在第5章中,我们将介绍使代码更易理解的一些具体技术。

过生日时得到一件礼物,或者彩票中奖,都是意外好事(惊喜)的例子。但是,当我们试图完成一件特定任务时,意外通常不是好事。

想象一下,你饿了,因此决定下单购买一些比萨。你拿出电话,找到比萨店的电话号码,拨通电话。很奇怪的是,电话那端沉默了很长时间,但最终还是接通了,有个声音询问你:想要什么?

“请来一份大的玛格丽塔比萨,外送。”

“好的,你的地址?”

半个小时以后,你收到外卖,打开包装一看却发现图1-3中的情景。

图1-3 如果你以为是在和一家比萨店通话,实际上却是一家墨西哥餐厅,那么你的订单仍然有意义,但送来的可能是意想不到的东西

哇,这真是意外。显然,有人将“margherita”(一种比萨的名称)误听成“margarita”(一种鸡尾酒的名称)。但这是件怪事,因为这家比萨店没有提供鸡尾酒。

原来,你手机上使用的定制拨号应用添加了一个新的“智能”功能。应用开发者发现,当用户拨打一家餐厅的电话却遇到忙线的情况时,80%的人会立刻致电另一家餐厅,因此,他们创建了一个节约时间的方便功能:当你拨打一个应用识别为餐厅的电话号码且遇到忙线,该应用将无缝地拨打电话簿中下一家餐厅的电话号码。

在这个例子中,下一家餐厅恰好是你最喜欢的墨西哥餐厅,而不是你以为正在拨打的比萨店。墨西哥餐厅肯定提供玛格丽塔鸡尾酒,而不是比萨。应用开发者的意图很好,也认为这种功能可以方便用户的生活,但他们创建了一个会带来某种意外的系统。我们依赖自己对电话的心理模型,根据听到的声音确定发生的事情。重要的是,如果我们听到语言应答,心理模型就会告诉我们已经接通刚刚拨打的电话号码。

定制拨号应用的这个新功能改变了应用的表现,超出我们的预期。它打破了我们的心理模型假设,即语音应答意味着我们已经接通刚刚拨打的电话号码。这个功能或许很有用,但因为其行为超出普通人的心理模型,就必须明确告知人们发生的情况,例如用语音信息告诉人们拨打的电话号码正忙,询问是否愿意拨打另一家餐厅的电话号码。

可以将这个定制拨号应用类比为一段代码。其他工程师在使用我们的代码时将名称、数据类型和常见约定作为线索,以构建一个心理模型,用于预测我们的代码以什么为输入、完成什么功能、返回什么结果。如果我们的代码行为超出这个心理模型,就很有可能导致软件潜藏缺陷。

在致电比萨店的例子中,即便在发生意外情况之后,一切似乎仍正常运转:你点了一个玛格丽塔比萨,餐厅也乐于效劳。直到很久以后,错误已无法纠正,你才发现无意间点了一杯鸡尾酒而不是一份比萨。这与软件系统中的代码完成意外工作时常常发生的情况很类似:因为代码调用者没有预料到这种意外,这些代码将一无所知地继续执行。在一段时间里,一切看起来都很正常,但随后出现可怕的错误,程序处于无效状态,或者将一个奇怪的值返回给用户。

即便有着最好的意图,编写提供一些有用或者“聪明”功能的代码仍有造成意外的风险。如果代码做了某些意外的事情,使用代码的工程师不会知道也不会思考处理那种情况的方法。这往往会导致系统“跛行”,直到在远离问题代码的地方出现明显的古怪表现。或许,这只会产生一个有些恼人的缺陷,但也可能造成破坏重要数据的灾难性问题。我们应该提防代码中的意外情况,并尽可能避免。

在第3章中,我们将看到,代码契约是一种有助于解决这个问题的基础技术。第4章在介绍软件错误时提到,如果不能正确提示或处理这些错误,就可能导致意外情况。第6章将关注避免意外的一些更具体的技术。

在电视机的背部,我们可能会看到如图1-4所示的接口。我们可以在这些接口里插入不同的线缆。重要的是,电视机厂商通过将不同的接口设计成不同的形状,可以防止用户将电源线插进HDMI接口中。

图1-4 电视机厂商有意将电视机背部的接口做成不同形状,以避免用户插入错误的线缆

想象一下,如果电视机厂商没有这么做,而是将每个接口做成相同的形状,将会出现什么情况。你认为会有多少人在电视机背部摸索的时候不小心将线缆插进错误的接口里?如果将HDMI线缆插到电源接口里,电视机可能无法工作,虽然让人烦恼,但也不算太可怕。但如果有人将电源线插进HDMI接口里,就会烧毁电视机的电路板。

我们所写的代码常常被其他代码调用,这有点像是一台电视机的背部。我们预计其他代码会“插入”某种东西,比如输入参数,或者在调用前将系统置于某个状态。如果将错误的东西“插入”代码,就可能造成某些破坏:系统崩溃、数据库永久性损坏或者丢失某些重要数据。即便没有造成破坏,代码也很有可能无法正常工作。我们的代码被调用是有原因的,插入不正确的内容,可能意味着一项重要的任务没有执行,或者某些古怪的行为没有引起注意。

通过编写很难或不可能被误用的代码,我们可以最大限度地提高代码持续正常工作的概率。针对这个问题,有许多实用的解决方法。第3章介绍的代码契约(类似于避免意外)是有助于编写难以误用的代码的基础技术。第7章将介绍编写难以误用的代码的一些更为具体的技术。

模块化意味着一个对象或系统由可独立替换的更小的组件组成。为了说明这个概念以及模块化的好处,我们考虑图1-5中的两个玩具。

图1-5 模块化的玩具很容易重新配置,而缝合起来的玩具则极难重新配置

图1-5左侧的玩具是高度模块化的。头部、手臂、手掌和腿都很容易独立替换,而不会影响到玩具的其他部分。相反,图1-5右侧的玩具是非模块化的。没有轻松的方法可以替换头部、手臂、手掌或腿。

模块化系统(如图1-5左侧的玩具)的特征之一是,不同组件有明确定义的接口,相互作用的点尽可能少。如果我们将一只手掌当成组件,那么左侧的玩具只有一个交互点和一个简单的接口:一根钉子,以及一个与之适配的小孔。而右侧的玩具在手掌和身体其他部位之间有一个极其复杂的接口:手掌和手臂上有20多圈相互交织的线。

现在想象一下,如果我们的任务是维护这些玩具,某天经理告诉我们一个新需求:手掌上要有手指。我们更愿意应对哪一个玩具/系统?

对于左侧的玩具,我们可以制造一只新设计的手掌,轻松地替换现有的手掌。如果两周以后,经理改变了主意,我们可以恢复玩具原来的配置,而不会产生任何麻烦。

至于右侧的玩具,我们可能不得不拿出剪刀,剪掉那20多圈线,然后直接将新的手掌缝到玩具上。在这个过程中,我们可能会损坏这个玩具,如果两周后经理改变主意,我们就要同样费尽力气将玩具恢复成原有配置。

软件系统和代码库与这些玩具非常相似。将代码分解为独立模块,其中两个相邻模块的交互发生在单一位置、使用明确定义的接口,往往是很有好处的。这有助于确保代码更容易适应变化的需求,因为一项功能的变化不需要对所有地方进行大量修改。

模块化系统通常也更容易理解和推演。因为系统被分解为容易控制的小功能块,各功能块之间的交互有明确的定义和文档。这增加了代码一开始就能正常工作,并在未来持续工作的可能性——因为工程师更不容易误解代码的作用。

在第2章中,我们将了解如何创建清晰的抽象层次,这是引导我们编写出更具模块化特性的代码的一种基础技术。在第8章中,我们还将了解一系列使代码更加模块化的具体技术。

可重用性和可推广性这两个概念很类似,但略有不同。

可重用性的含义是某个系统可在多种场景下用于解决同一个问题。手钻是一种可重用工具,因为它可以在墙、地板和天花板上钻孔。问题是相同的(需要钻一个孔),但场景不同(钻墙、钻地板和钻天花板)。

可推广性的含义是某个系统可用于解决多个概念相近但有细微差异的问题。手钻也是具有可推广性的工具,因为它可以用于钻孔,也可以将螺钉固定到某个物体上。制造商认识到,旋转是适用于转孔和固定螺钉的通用问题,因此它们造出可通用于这两个问题的工具。

在手钻的例子中,我们能立刻认识到这两个特性的好处。想象一下,如果我们需要4种不同的工具。

只能在平举状态工作的钻孔机——只能用于钻墙。

只能垂直向下工作的钻孔机——只能用于钻地板。

只能垂直向上工作的钻孔机——只能用于钻天花板。

用来固定螺钉的电动螺丝刀。

我们必须花很多钱购买这一套4种工具,将更多的东西带在身上,给4组电池充电——这都是浪费。幸亏有人发明了既可重用又可推广的手钻,我们只需要一种工具就能完成上述所有工作。不用猜也知道,手钻在这里又是对代码的一种类比。

创建代码需要花费时间和精力,一旦创建完毕,还需要持续投入时间和精力进行维护。创建代码也并非没有风险:尽管我们小心翼翼,编写的一些代码仍会包含缺陷,写得越多,出现缺陷的可能性越大。重点在于,我们在代码库中留下的代码行数越少越好。这听起来可能有些奇怪,我们不是通过写代码得到报酬的吗?但实际上,我们得到工资,是因为能够解决某个问题,代码只是一种手段。如果我们可以解决问题,同时花费更少的精力,降低我们不小心引入缺陷而导致其他问题的概率,就太好了。

编写可重用、可推广的代码,我们(和其他人)就可以在代码库的多个地方和场景中使用它们,解决不止一个问题。这能节约时间和精力,并使我们的代码更加可靠,因为我们往往重用已在外部经过考验的逻辑,其中的缺陷可能已经被发现和修复。

更具模块化特性的代码往往也有更好的可重用性和可推广性。与模块化相关的章节与可重用性和可推广性的主题关系紧密。此外,第9章将介绍一些提高代码可重用性、可推广性的专用技术和考虑因素。

正如我们在前面的软件开发与部署过程(见图1-2)中所见到的,在确保最终不会将有缺陷和不完善的功能投入运行的过程中,测试是至关重要的一环。它们往往是这一过程中两个关键点的主要保障(见图1-6)。

防止有缺陷或者不完善的功能提交到代码库。

确保阻止有缺陷或不完善的功能发行并投入运行。

因此,测试对确保代码可用并持续正常工作是必不可少的。

图1-6 为了最大限度地防止有缺陷和不完善的功能进入代码库,并确保它们不会对外发行,测试至关重要

在软件开发中,测试的重要性如何强调都不为过。你以前肯定多次听到这一说法,很容易将其视为老生常谈,但它确实重要。正如我们在本书的很多地方看到的那样。

软件系统和代码库往往太过庞大和复杂,一个人不可能了解所有细节。

人(即便是智力超群的工程师)都会犯错。

这或多或少都是生活中的事实。除非我们用测试来锁定代码的功能,否则这些功能就会习惯性地与我们(以及我们的代码)纠缠在一起。

代码质量的这一支柱包含两个重要的概念:“编写可测试的代码”以及“适当测试”。测试和可测试性相关,但考虑的因素不同。

测试——顾名思义,这与测试我们的代码或者软件有关。测试可能是人工进行,也可能是自动进行。作为工程师,我们通常努力编写测试代码来执行“真实”代码,并检查一切表现是否如同预期。测试有不同级别。你可能使用的3种最常见的测试级别如下。(请注意,这并不是完整的列表。测试有许多分类方法,不同组织往往使用不同的术语。

单元测试——这种测试通常测试代码的小单元(如单个函数或类)。单元测试是测试工程师在日常编程中最经常使用的测试级别,也是本书唯一详细介绍的测试级别。

集成测试——系统通常由多个组件、模块或子系统组成。将这些组件和子系统连接到一起的过程称为集成。集成测试试图确保这些集成正常工作,而且一直保持正常。

端到端(E2E)测试——测试整个软件系统从头至尾的典型流程。如果待测软件是一个在线购物系统,E2E测试的一个例子是自动驱动浏览器,确保用户能够完成一次购物流程。

可测试性——这指的是“真实代码”(相对于测试代码),并描述该代码在测试中的表现。某个事物“可测试”的概念在子系统或系统级别上也适用。可测试性往往与模块化高度关联,模块化程度越高的代码(或系统)越容易测试。想象一下,某汽车制造商正在开发一种紧急行人防撞制动系统。如果该系统的模块化程度不高,测试它的唯一方式可能是将其安装在真实的汽车上,将车开到一个真人面前,检查车辆是否会自动停下。如果情况果真如此,那么该系统所能测试的场景有限,因为每次测试的成本非常高:制造一辆整车,租用一条测试道路,并让一个真人冒险扮演路上的行人。如果这种紧急制动系统是一个单独的模块,可在真实车辆之外运行,可测试性就更高了。现在测试可以通过如下方式进行:向该系统提供预先录制的行人走出的视频,检查它是否为紧急制动系统输出正确的信号。这样的测试非常简易、经济且安全,可以对成千上万种不同的行人状况进行测试。

如果代码不可测试,也就不可能对其进行“适当”测试了。为了确保我们编写的代码是可测试的,最好在编写代码时不断地问自己一个问题:“我们将如何测试这些代码?”因此,测试不应该是“马后炮”,而应该是编写代码各个阶段不可分割的基本组成部分。第10章和第11章介绍的都是关于测试的内容,但因为测试对编写代码必不可少,所以我们在本书的许多地方都会提到。

注意:测试驱动开发

因为测试是代码编写工作中必不可少的部分,一些工程师倡导在编写代码之前先编写测试的做法。这是测试驱动开发(Test-Driven Development,TDD)过程所支持的做法之一。我们将在10.5节中进一步讨论这个问题。

软件测试是一个很广泛的主题,坦率地说,本书无法做到面面俱到。在本书中,我们将介绍代码单元测试中重要且常被忽视的特征,因为它们在日常编程过程中通常非常有用。但请注意,直到本书的最后,我们对软件测试的介绍也只是皮毛。

这个问题的答案是,在很短的一段时期,编写高质量代码似乎会拖慢我们的进度。与按照头脑中首先闪现的念头编写代码相比,高质量的代码需要更多的思考和努力。但如果我们编写的不仅仅是运行一次就抛之脑后的小程序,而是更有实质性的软件系统,那么编写高质量的代码通常会在中长期加快开发进程。

想象一下,我们要在家里装一块搁板。有一种“恰当”的方法,也有一种快速的“变通”方法。

“恰当”的方法——我们在墙体立柱或砖石等坚固的东西上钻孔、固定螺钉,将支架固定在墙上。然后,我们将搁板安装在这些支架上。花费时间:30min。

“变通”的方法——购买一些胶水,将搁板粘在墙上。花费时间:10min。

看起来,用“变通”方法装搁板可以节约20min,也不会用到手钻。我们选择了这种快速的方法。现在,我们来考虑接下来发生的事情。

我们将搁板粘在墙面上,但墙面材料最有可能是一层灰泥。灰泥并不坚固,很容易开裂并大块大块地剥落。一旦我们开始使用搁板,所放东西的重量很可能导致灰泥开裂,搁板将掉下来并带下来大块的灰泥。现在,不仅我们的搁板无法使用,而且需要重新粉刷墙面(这项工作即便不需要几天,至少也要几个小时)。即便奇迹出现,搁板没有掉下来,我们也因为采用了“变通”方法而给未来带来问题。想象如下两种场景。

我们发现搁板放得不够水平(缺陷)。

对于有支架的搁板,我们只需要在支架和搁板之间加入一个较小的垫片。花费时间:5min。

对于用胶水粘上的搁板,我们必须将它从墙上揭下来,这会带下来一大块灰泥。现在,我们必须重新粉刷墙面,再将搁板装回去。花费时间:几个小时,甚至几天。

我们决定重新装饰房间(新需求)。

我们可以卸下螺钉,将带支架的搁板拆下来。重新装饰房间以后,我们再将搁板放回去。与搁板相关的工作花费时间:15min。

对于用胶水粘上的搁板,我们要么不动搁板,那么它有滴上油漆、在我们必须油漆或者铺上墙纸的地方留下不干净边缘的风险。我们也可以将搁板揭下来,那么必须重新刷上灰泥。我们只能在低劣的重新装饰工作和花几小时(甚至几天)重新涂抹墙面之中选择一个。

你应该明白了吧。最初看起来,按照“恰当”的方法做,安装一个带支架的搁板似乎毫无意义地浪费了20min,但从长期看,它很有可能节省许多时间和减少麻烦。在将来的重新装饰计划中,我们还会看到,一开始采用快速的“变通”方法,以后将迫使我们走上一条采用更多权宜之计的道路,比如在搁板周围刷油漆或者贴墙纸,而不是在重新装饰时取下搁板。

编写代码与此很相似。根据我们脑海里浮现的第一个想法编程,而不考虑代码质量,很可能一开始会节省一些时间。但我们很可能得到一个脆弱、复杂的代码库,它将越来越难以理解或推测。添加新功能或修复缺陷将变得越来越难,因为我们不得不应付破坏的情况,并重新设计一切。

你以前一定听过“欲速则不达”这句话,这是通过对生活中许多事物的观察得出的经验,在没有考虑清楚正确的方法之前,过于匆忙的行动往往导致错误,从而降低总体的速度。这也很好地总结了编写高质量代码能加快开发速度的原因,不要为了速度而鲁莽行动。

要创造优秀的软件,我们必须编写高质量代码。

代码变成投入运行的软件之前,通常必须经过多个检查与测试阶段(有时候是人工进行的,有时是自动进行的)。

这些检查有助于避免有缺陷、不完整的功能交付到用户手中或者应用到关键业务系统上。

在编写代码的每个阶段都考虑测试是很好的习惯,不应该将其视为“马后炮”。

一开始,编写高质量代码看似会拖慢我们的进度,但往往能在中长期加快开发速度。

微信扫码关注【异步社区】微信公众号,回复“e59641”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


相关图书

有限元基础与COMSOL案例分析
有限元基础与COMSOL案例分析
程序员的README
程序员的README
现代控制系统(第14版)
现代控制系统(第14版)
现代软件工程:如何高效构建软件
现代软件工程:如何高效构建软件
GitLab CI/CD 从入门到实战
GitLab CI/CD 从入门到实战
科学知识图谱:工具、方法与应用
科学知识图谱:工具、方法与应用

相关文章

相关课程