【51CTO.com快译】最近,我们公司内部一直在讨论如何写出整洁的代码的相关研讨会,他们认为一份整洁的代码能为团队开发,后期维护,重构奠定了良好的基础,其质量也是可靠的。因此各小组以如何建立并监督编码标准展开了大量的讨论。虽然我同意这类作法确实有一定的作用,但我认为整洁代码最核心的关键并不是这个。因此,以下内容是我个人对整洁代码的理解与看法。
(免责声明:以下内容并非我个人原创。这一切都来自众多伟大程序员的指导思想。但我认为,重要内容值得一再强调。)
首先,我个人认为整洁代码这一说法并不准确。这一术语很容易误导大家以为仅仅是让代码看起来更加整洁就可以了,而这显然是很肤浅的表面现象。相反,我认为将它称为“宜居型代码”更为合适,正如Richard Gabriel所说:
宜居型代码是源代码的一种特性,即允许程序员、编码者、bug修复者以及其他人在***阅读代码的人能很清晰的明白这里在干什么,而不是隐涩难懂,并能很快的加以调整。
宜居型代码会让人有种宾至如归的体验,只有这样,开发者才能在无需深入考量的前提下快速着手工作。
虽然良好的代码格式,会使得我们阅读更容易且查找更快速,但其本质的宜居型是关键。在我看来,宜居型代码是指代码能随着业务的需求任意改变的。因此,在小规模业务要求调整代码时只需要投入低成本的代价就可实现,而当大规模业务要求调整代码时只需要再投入较多的开发成本即可实现——因为新需求与现有代码库不相契合,所以无法共享原有的代码。
考虑到这一定义,我认为宜居型代码的关键在于整体结构。而只有顶层设计(架构)能够将宜居型的特性变为现实。而***实现途径包括以下几项:
- 应用程序要被划分为多个(不应过多)模块。
- 每个模块代表着其领域里的一个特定意义, 其命名要一目了然,且不存在歧义,并要确保***阅读代码的人能快速了解其作用。
- 各模块要拥有一个经过良好定义的接口,同样要有一个特定且唯一的名字来命名。
- 每个模块的生命周期和各个模块之间的关系是以声明方式在应用程序的入口点中表示。具体来讲,应用入口点必须明确声明这些模块是以何种方式来对接的,并要提供必要的业务价值。
- 各模块间的共生性要在各层级代码得到明确表达。
(这部分内容其实就是对结构化程序设计方法的再次重申。正如之前提到,我并没有创造任何新鲜概念。)
在我看来,这些模块属于形式与功能的组合——命名是区分应用区,而行为是描述业务。不同模块间的关系应当得到声明,且不存在任何隐藏或任何歧义,直接了当表明关系。
我还认为,这一定义同样适用于递归。着眼于任何***模块,其中每个模块都应与整体应用显示出同样的特性:
- 拆分为多个(不能太多)子模块。
- 每个子模块要拥有一个经过良好定义的接口。
- 各子模块的关系与生命周期应在该模块的入口点中声明。
- 各子模块间的共生性应在代码中得到明确表达。
这些嵌套模块可以作为应用、微服务、软件包、命名空间、绑定上下文、聚合、模块、对象乃至功能形式存在——这一切皆可被视为“封装单元”或者“对象”。
-
这一原则与Page-Jones重构算法能够相互印证:
-
将应用划分为多个(不能太多)模块。
-
移除各模块间的全部高阶共生性。
-
递归直至无意义。
任何模块的拆分其子模块都能代表着另一组实现。并且我希望能够将各个模块作为一个黑匣子看待,且能在无冲突前提下对黑匣子边界进行重构。这需要该模块的接口有个一良好定义(例如通过自动化测试实现),且各模块间及子模块间的共生性明确且可理解。
在我看来,随着层级降低,利用这一算法的价值也将随之降低。换言之,明确顶层模块的关系,其价值要高于明确底层子模块间的关系。这样的应用更易于变更,特别是考虑到顶层代码要采取简单的表达方式。虽然整洁的低级别模块同样能带来好处,但实现应用宜居型的整体收益相对较低。
作为实现手段之一,我认为面向消息型编程能够在各类规模下能达到良好的收效。我之所以将应用划分成多个模块,单纯是为了让各模块间的通信变得简单且意义明确。模块的存在只是为了隐藏消息以及层的变更是如何实际“生效”的。Michael Feathers提出的Naked CRC技术同样能够很好地实现宜局型模块的通信。
遗憾的是,我所见过的大部分代码库都粗暴地违反了以上各种实现模式。其通常包含着数百个毫无结构性可言的类,这些类全部存在于同一抽象及可访问层内。这样的代码库绝无宜人性可言,且很难向其中添加新的类或者概括其中现有类的作用。这类应用同样难于重构,因为大多数“单元测试”机制着眼于实现选择的而非业务要求。而且由于不具备顶层封装的单元,因此我们很难理解其整体设计思路。
原文标题:What Is Clean Code?
原文作者:Kevin Rutherford
【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】