想快点完成;赶时间;赶紧做完手上的工作好接着做下一件工作等。
我们都曾经说过有朝一日再回头清理,当然在哪些日子里,我们都没有听过勒布朗法则:稍后等于永远不。
花时间保存代码整洁不但关乎效率,还关乎生存。
你可曾遇到过某种严重到要花数个星期来做本来只需数小时即可完成的事的混乱状况?你可曾见过本来只需做一行修改,结果却涉及上百个模块的情况?这种事太常见了。怎样会发生这种事?理由有很多,但最终表现的是我们太不专业了。
产品他们会奋力护卫进度和需求,这是他们该干的,我们则当以同等的热情护卫代码。
作者列举了业界大佬对整洁代码的理解,每一个观点都值得好好体会。
Bjarne Stroustrup观点:我喜欢优雅和高效的代码。代码逻辑应当直接了当,令陷阱难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没有规矩的优化,搞出一堆混乱来。整洁代码只做好一件事。
提炼和补充:
Grady Booch观点:整洁代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。
提炼和补充:
Dave Thomas观点:整洁的代码应可由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰的、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需的信息均可通过代码自身清晰表达。
提炼和补充:
Michael Feather观点:我可以列出我留意到的整洁代码的所有特点,他其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码的作者什么都想到了。如果你企图改进它,总会回到原点,赞叹某人留给你的代码——全心投入的某人留下的代码。
提炼和补充:
Ron Jeffries观点:近年来,我开始研究贝克的简单代码规则,差不多也都做琢磨透了。简单代码,依据重要顺序:
Ward Cunningham观点:如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专门为解决那个问题而存在的,就可以称之为漂亮的代码。
注意命名,而且一旦发现有其他更好的名称,就换掉旧的。
起名字规则:
- 别害怕长名称;
- 别害怕花时间起名字;
- 命名方式要保存一致;
注释的恰当用法是弥补我们在用代码表达意图是遭遇的失败。
注释不能美化糟糕的代码
写注释的常见动机之一是糟糕的代码的存在。带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎二复杂的代码像样得多。与其花时间编写解释你写的糟糕的代码的注释,不如花时间清理那堆糟糕的代码。
用代码来阐述
用代码解释意图
好注释
- 法律信息
- 提供信息的注释
- 对意图的解释
- 阐释
- 警示
你应该保持良好的代码格式,你应该选用一套管理代码格式的简单规则,然后贯彻这些规则。
横向格式
水平方向上的区隔与靠近,用空格字符将彼此紧密相关的事物连接到一起,也用空格字符把相关性较弱的事物区隔开。水平对齐缩进.
团队规则
每个程序员都有自己喜欢的格式规则,但如果在团队中工作,就是团队说了算。
对象暴露行为,隐藏数据,便于添加新对象类型而无须修改既有行为,同时难以在既有对象中添加新行为;数据结构暴露数据。没有明显的行为,便于向既有数据结构添加新的行为,同时难以向既有函数添加新的数据结构。
整洁的边界,边界上的代码需要清晰的分割和定义了期望的测试。一个避免我们的代码过多地了解,第三方代码中的特定信息。依靠你能控制的东西,好过依靠你控制不了的东西,免得日后受它的控制。
保持测试整洁
测试必须随生产代码的演进而修改。测试越脏,就越难修改。测试代码越缠结,你就越有可能花更多时间塞进新的测试,而不是编写新的生产代码。
整洁的测试
整洁测试三要素:可读性、可读性和可读性。
保存内聚性就会得到许多短小的类,将大函数拆分为许多小函数时,往往也是将类拆分为多个小类的时机。程序会更加有组织,也会拥有更为透明的结构
依赖反转原则(DIP)主要想告诉我们的是,如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
在应用DIP时,我们也不必考虑稳定的操作系统或者平台设施,因为这些系统接口很少会有变动。
我们主要应该关注的是软件系统内部那些会经常变动的(volatile)具体实现模块,这些模块是不停开发的,也就会经常出现变更。
我们每次修改抽象接口的时候,一定也会去修改对应的具体实现。但反过来,当我们修改具体实现时,却很少需要去修改相应的抽象接口。所以我们可以认为接口比实现更稳定。
如果想要在软件架构设计上追求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现。下面,我们将该设计原则归结为以下几条具体的编码守则:
如果想要遵守上述编码守则,我们就必须要对那些易变对象的创建过程做一些特殊处理,这样的谨慎是很有必要的,因为基本在所有的编程语言中,创建对象的操作都免不了需要在源代码层次上依赖对象的具体实现。
在大部分面向对象编程语言中,人们都会选择用抽象工厂模式来解决这个源代码依赖的问题。
随着内容的进一步深入,以及我们对高级系统架构理论的进一步讨论,DIP出现的频率将会越来越高。在系统架构图中,DIP通常是最显而易见的组织原则。
组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。设计良好的组件都应该永远保持可被独立部署的特性,这同时也意味着这些组件应该可以被单独开发。
我们常常会在程序运行时插入某些动态链接文件,这些动态链接文件所使用的就是软件架构中的组件概念。在经历了50年的演进之后,组件化的插件式架构已经成为我们习以为常的软件构建形式了。
究竟是哪些类应该被组合成一个组件呢?在本章中,我们会具体讨论以下三个与构建组件相关的基本原则:
软件复用的最小粒度应等同于其发布的最小粒度。
我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。
这其实是SRP原则在组件层面上的再度阐述。SRP原则:“一个类不应该同时存在着多个变更原因”,CCP原则也认为一个组件不应该同时存在着多个变更原因。
CCP的主要作用就是提示我们要将所有可能会被一起修改的类集中在一处。也就是说,如果两个类紧密相关,不管是源代码层面还是抽象理念层面,永远都会一起被修改,那么它们就应该被归属为同一个组件。通过遵守这个原则,我们就可以有效地降低因软件发布、验证及部署所带来的工作压力。
不要强迫一个组件的用户依赖他们不需要的东西。
上述三个原则之间彼此存在着竞争关系。REP和CCP原则是黏合性原则,它们会让组件变得更大,而CRP原则是排除性原则,它会尽量让组件变小。软件架构师的任务就是要在这三个原则中间进行取舍。
下图,是一张组件聚合三大原则的张力图。图的边线所描述的是忽视对应原则的后果。
优秀的软件架构师应该能在上述三角张力区域中定位一个最适合目前研发团队状态的位置,同时也会根据时间不停调整。例如在项目早期, CCP原则会比REP原则更重要,因为在这一阶段研发速度比复用性更重要。
般来说,一个软件项目的重心会从该三角区域的右侧开始,先期主要牺牲的是复用性。然后,随着项目逐渐成熟,其他项目会逐渐开始对其产生依赖,项目重心就会逐渐向该三角区域的左侧滑动。
过去,我们对组件在构建过程中要遵循的组合原则的理解要比REP、 CCP、CRP这三个原则更有限。我们最初所理解的组合原则可能完全基于单一职责原则。然而,本章介绍的这三个原则为我们描述了一个更为复杂的决策过程。在决定将哪些类归为同一个组件时,必须要考虑到研发性与复用性之间的矛盾,并根据应用程序的需要来平衡这两个矛盾,这是一件很不容易的事。而且,这种平衡本身也在不断变化。也就是说,当下适用的分割方式可能明年就不再适用了。所以,组件的构成安排应随着项目重心的不同,以及研发性与复用性的不同而不断演化。
组件依赖关系图中不应该出现环。
有两种解决方案:“每周构建” 和 “无依赖环原则(ADP)”
存在的问题,随着项目越来越大,集成工作会越来越难以按时完成。
解决办法将研发项目划分为一些可单独发布的组件,这些组件可以交由单人或者某一组程序员来独立完成。当有人或团队完成某个组件的某个版本时,他们就会通过发布机制通知其他程序员,并给该组件打一个版本号,放入一个共享目录。这样一来,每个人都可以依赖于这些组件公开发布的版本来进行开发,而组件开发者则可以继续去修改自己的私有版本。
每当一个组件发布新版本时,其他依赖这个组件的团队都可以自主决定是否立即采用新版本。若不采用,该团队可以选择继续使用旧版组件,直到他们准备好采用新版本为止。
要求:必须控制好组件之间的依赖结构,绝对不能允许该结构中存在着循环依赖关系。
有两种机制可以做到打破循环依赖。
组件结构图是不可能自上而下被设计出来的。它必须随着软件系统的变化而变化和扩张,而不可能在系统构建的最初就被完美设计出来。
组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性与维护性方面的一张地图。这就是组件的依赖结构图不能在项目的开始阶段被设计出来的原因——当时该项目还没有任何被构建和维护的需要,自然也就不需要一张地图来指引。除此之外,我们还希望将项目变更所影响的范围被限制得越小越好,因此需要应用单一职责原则(SRP)和共同闭包原则(CCP)来将经常同时被变更的类聚合在一起。
组件结构图中的一个重要目标是指导如何隔离频繁的变更。我们不希望那些频繁变更的组件影响到其他本来应该很稳定的组件。
如果我们在设计具体类之前就来设计组件依赖关系,那么几乎是必然要失败的。因为在当下,我们对项目中的共同闭包一无所知,也不可能知道哪些组件可以复用,这样几乎一定会创造出循环依赖的组件。因此,组件依赖关系是必须要随着项目的逻辑设计一起扩张和演进的。
依赖关系必须要指向更稳定的方向。
一个组件的抽象化程度应该与其稳定性保持一致。
本章介绍了各种可用于依赖关系管理的指标,它们可以被用来量化分析某个系统设计与“优秀”设计模式之间的契合度。根据以往的经验,组件之间有些依赖关系是好的,有些依赖关系则是不好的,这些经验最后都会体现在这个设计模式中。当然,指标并不等同于真理,它只是对我们所定义标准的一个衡量。这些指标肯定是不完美的,但是我希望它们对读者有价值。