第1章 强制设计:约束、契约和断言
Imperfect C++中文版
在我们设计软件时,我们希望软件根据设计而进行使用。这并非一句空话。在大多数情况下,很容易发生以意料之外的方式来使用软件,而这么做的结果往往是令人失望的。
大多数软件的文档几乎都是不完整,甚至是过时的,我坚信你也有这方面的经验。这并非单纯的错误或缺失,“如果还有比没有文档更糟的情形,那就是文档是错误的”[Meye1997]。如果被使用的组件比较简单,使用得当,或者说是标准的或被普遍使用的,那么没有文档倒也不是什么大问题。例如,如果许多程序员需要一而再、再而三地查找C库函数malloc()的用法,那可真算是奇闻怪谈了。然而,这种情况毕竟很少。我曾经遇到一些程序员,他们非常有经验,但对malloc()的兄弟realloc()和free()之间的细微差别却并不那么熟悉。
对于这些问题,解决的方式很多。其一是通过增强的参数验证,使软件组件具有更强的抵抗错误的能力,但这种方式通常不那么有吸引力,因为它会损及性能,还倾向于滋生坏习惯。制作良好的文档并让它们保持更新当然是解决方案的一个重要组成部分,然而这种做法是远远不够的,因为它是“非强制性”的。此外,要想写出好文档也是一件极其困难的事情[Hunt2000]。软件越复杂,其原始作者要想把自己摆在对该软件懵懂无知的处境从而便于写出更好的说明文档就越不可能,而独立的技术作者要想抓住其所有的细微之处就更加困难了。因此,当情况不再单纯时,一个确保正确使用代码的更好的方式显然是必不可少的。
如果编译器能够为我们找出错误那就更可取了。事实上,本书中的相当一部分内容都是关于如何驱使和利用编译器,令它在碰到糟糕的代码时卡壳,从而便于我们在编译期及早抓住错误。但愿你能够意识到花几分钟来安抚编译器比花上几个小时和调试器纠缠要好得多。正如Kernighan和Pike在The Practice of Programming[Kenm1999]中所说的那样,“无论你喜欢与否,调试是一门我们经常要实践的艺术……如果不产生bug就好了,所以我们尝试在第一时间就把代码写正确,从而尽量避免bug的产生。”由于我并不比其他软件工程师更勤快,因此我总是尽量让编译器帮我做事情。从长远来看,“苦行僧”式的编程是比较容易的选择。然而,并非所有的错误都能够在编译期查出来。在这种情况下,我们需要求助于运行期机制。一些语言(例如D和Eiffel)提供了内建的机制,即通过“契约式设计(Design by Contract,Dbc)”来确保软件按照其设计而被使用。此项技术的先驱是Bertrand Meyer[Meye1997],它源于程序的形式验证。契约式设计要求为软件组件指定“契约”,这些契约会在程序运行过程中的某些特定点被强制执行。契约在很多方面都可以作为文档的替代品,因为它们无法被忽略,并且是自动进行验证的。此外,通过遵循特定的语法约定,它就可以和自动化文档工具合作,我们将会在1.3节对此进行讨论。
实施“强制(enforcement)”的机制之一是断言(assertion),包括广为人知的运行期断言,以及较少为人知然而甚至更为有用的编译期断言。本书中两者均得到了大量的使用,因此我们将会在1.4节对这种重要的工具进行详细的观察。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。