优良的代码显然不是制作优秀软件的唯一要素,但是主要的要素之一。我们可能拥有世界上最好的产品和营销团队,部署了最好的平台,并以最好的框架来构建软件,但归根结底,一款软件所做的一切,都是因为有人写了一段代码才得以实现的。
孤立地看,工程师们每天编写代码时所做出的决策都很小,有时显得微不足道,但它们共同决定了软件的好坏。如果代码中包含了缺陷、配置错误或者无法恰当地处理错误情况,由此构建的软件就可能有许多缺陷,甚至无法正常工作。
代码质量的六大支柱如下:
〓● 编写易于理解(可读)的代码;
如果我们的代码可读性很差,其他工程师就不得不花费很多时间来解读它。他们很有可能错误地理解它的作用,或者遗漏一些重要的细节。如果发生这种情况,代码评审期间就不太可能发现缺陷,而在其他人修改我们的代码、添加新功能时,就有可能引入新缺陷。软件的功能都是基于代码来完成的。如果工程师无法理解代码的作用,也就几乎不可能确定软件能否正常工作。正如食谱一样,代码必须易于理解。
〓● 避免意外;
即便有着最好的意图,编写提供一些有用或者“聪明”功能的代码仍有造成意外的风险。如果代码做了某些意外的事情,使用代码的工程师不会知道也不会思考处理那种情况的方法。这往往会导致系统“跛行”,直到在远离问题代码的地方出现明显的古怪表现。或许,这只会产生一个有些恼人的缺陷,但也可能造成破坏重要数据的灾难性问题。我们应该提防代码中的意外情况,并尽可能避免。
〓● 编写难以误用的代码;
通过编写很难或不可能被误用的代码,我们可以最大限度地提高代码持续正常工作的概率。针对这个问题,有许多实用的解决方法。
〓● 编写模块化的代码;
软件系统和代码库与这些玩具非常相似。将代码分解为独立模块,其中两个相邻模块的交互发生在单一位置、使用明确定义的接口,往往是很有好处的。这有助于确保代码更容易适应变化的需求,因为一项功能的变化不需要对所有地方进行大量修改。
模块化系统通常也更容易理解和推演。因为系统被分解为容易控制的小功能块,各功能块之间的交互有明确的定义和文档。这增加了代码一开始就能正常工作,并在未来持续工作的可能性——因为工程师更不容易误解代码的作用。
〓● 编写可重用、可推广的代码;
编写可重用、可推广的代码,我们(和其他人)就可以在代码库的多个地方和场景中使用它们,解决不止一个问题。这能节约时间和精力,并使我们的代码更加可靠,因为我们往往重用已在外部经过考验的逻辑,其中的缺陷可能已经被发现和修复。
〓● 编写可测试的代码并适当测试。
如果代码不可测试,也就不可能对其进行“适当”测试了。为了确保我们编写的代码是可测试的,最好在编写代码时不断地问自己一个问题:“我们将如何测试这些代码?”因此,测试不应该是“马后炮”,而应该是编写代码各个阶段不可分割的基本组成部分。
其他工程师与代码契约
1、你的代码和其他工程师的代码
如果你以团队一员的身份编写代码,你所编写的代码很可能建立在其他工程师编写的代码层次的基础上,其他人也可能以你的代码为基础构建新的代码层次。如果你在工作期间解决了各种各样的子问题,并将其分解为清晰的抽象层次,其他工程师也有可能重用其中一些代码,去解决你可能没有考虑过的、完全不同的问题。
2、其他人如何领会你的代码的使用方法
当其他工程师需要利用你的代码或者修改一些对其有依赖的代码,就必须领会你的代码的使用方法及其功能。具体地说,他们必须理解如下要素:
〓● 在哪些场景下,他们应该调用你提供的各种函数;
〓● 你创建的类代表什么,应该在什么时候使用;
〓● 他们应该以什么值调用;
〓● 你的代码将执行什么操作;
〓● 你的代码可能返回什么值。
3、代码契约
工程师有时会发现,将关于代码契约的术语分成不同类别很有用。
〓● 先决条件——调用代码时应该满足的条件,例如系统应处在的状态和提供给代码的输入。
〓● 后置条件——调用代码后应该成立的条件,例如系统被置于某个新状态或者返回某些值。
〓● 不变量——对比代码调用前后的系统状态时应该保持不变的事物。
即便你不是有意地采用契约式编程,甚至从未听说过这个术语,你编写的代码也肯定存在某种契约。如果你编写了一个有输入参数、返回某个值或修改一些状态的函数,就创建了一个契约,因为你在代码调用者身上加注了一项义务:建立某种状态或者提供输入(先决条件),并给予他们关于将要发生的事情或返回值的预期(后置条件)。
当工程师不知道代码契约的某些或者全部条款时,就会出现问题。在编写代码时,重要的是考虑契约的内容,以及如何确保使用代码的每个人都了解并遵循契约。
4、检查和断言
使用编译器强制代码契约的一种替代方法是使用运行时强制。这通常不像编译时强制那么健全,因为代码契约漏洞的发现取决于一项测试(或者一个用户)运行代码时遇到的问题。这与编译时强制形成了鲜明对比,后者从一开始就在逻辑上消除了违反契约的可能性。
尽管如此,在有些情况下,没有切合实际的手段能用编译器强制实施一个契约。发生这些情况时,以运行时检查强制实施契约好于没有强制手段。
以上内容摘自《好代码 ,坏代码》第一章,第三章。
好代码 ,坏代码
Google开发工程师从零讲解高质量代码,整合作者及团队多年的软件开发实践经验,通过50+条锦囊妙计、100+个案例,帮你轻松理解和掌握编程技能。
本书分享的实用技巧可以帮助你编写鲁棒、可靠且易于团队成员理解和适应不断变化需求的代码。内容涉及如何像高效的软件工程师一样思考代码,如何编写读起来像一个结构良好的句子的函数,如何确保代码可靠且无错误,如何进行有效的单元测试,如何识别可能导致问题的代码并对其进行改进,如何编写可重用并适应新需求的代码,如何提高读者的中长期生产力,同时还介绍了如何节省开发人员及团队的宝贵时间,等等。
养成好的编程习惯,一定要必备这两本程序员经典书。
重构:改善既有代码的设计(第2版)
本书是一本为专业程序员编写的重构指南。我的目的是告诉你如何以一种可控且高效的方式进行重构。你将学会如何有条不紊地改进程序结构,而且不会引入错误,这就是正确的重构方式。
按照传统,图书应该以概念介绍开头。尽管我也同意这个原则,但是我发现以概括性的讨论或定义来介绍重构,实在不是一件容易的事。因此,我决定用一个实例作为开路先锋。第1章展示了一个小程序,其中有些常见的设计缺陷,我把它重构得更容易理解和修改。其间你可以看到重构的过程,以及几个很有用的重构手法。如果你想知道重构到底是怎么回事,这一章不可不读。
第2章讨论重构的一般性原则、定义,以及进行重构的原因,我也大致介绍了重构面临的一些挑战。第3章由Kent Beck介绍如何嗅出代码中的“坏味道”,以及如何运用重构清除这些“坏味道”。测试在重构中扮演着非常重要的角色,第4章介绍如何在代码中构筑测试。
从第5章往后的篇幅就是本书的核心部分——重构名录。尽管不能说是一份巨细靡遗的列表,却足以覆盖大多数开发者可能用到的关键重构手法。
代码整洁之道
本书大致可分为3个部分。前几章介绍编写整洁代码的原则、模式和实践。这部分有相当多的示例代码,读起来颇具挑战性。读完这几章,就为阅读第2部分做好了准备。如果你就此止步,只能祝你好运啦!
第2部分最需要花工夫。这部分包括几个复杂性不断增加的案例研究。每个案例都清理一些代码——把有问题的代码转化为问题少一些的代码。这部分极为详细。你的思维要在讲解和代码段之间跳来跳去。你得分析和理解那些代码,琢磨每次修改的来龙去脉。
你付出的劳动将在第3部分得到回报。这部分只有一章,列出从上述案例研究中得到的启示和灵感。在遍览和清理案例中的代码时,我们把每个操作理由记录为一种启示或灵感。我们尝试去理解自己对阅读和修改代码的反应,尽力了解为什么会有这样的感受、为什么会如此行事。结果得到了一套描述在编写、阅读、清理代码时思维方式的知识库。
如果你在阅读第2部分的案例研究时没有好好用功,那么这套知识库对你来说可能所值无几。在这些案例研究中,每次修改都仔细注明了相关启示的标号。这些标号用方括号标出,如:[H22]。由此你可以看到这些启示在何种环境下被应用和编写。启示本身不值钱,启示与案例研究中清理代码的具体决策之间的关系才有价值。
如果你跳过案例研究部分,只阅读了第1部分和第3部分,那就不过是又看了一本关于写出好软件的“感觉不错”的书。但如果你肯花时间琢磨那些案例,亦步亦趋——站在作者的角度,迫使自己以作者的思维路径考虑问题,就能更深刻地理解这些原则、模式、实践和启示。这样的话,就像一个熟练地掌握了骑车的技术后,自行车就如同其身体的延伸部分那样;对你来说,本书所介绍的整洁代码的原则、模式、实践和启示就成为了本身具有的技艺,而不再是“感觉不错”的知识。