第 2 章 有意义的命名
-
名副其实: 变量、函数或类的名称应该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算名副其实。
代码的模糊度:即上下文在代码中未被明确体现的程度。 -
避免误导: 提防使用不同之处较小的名称。
-
做有意义的区分: 要区分名称,就要以读者能鉴别不同之处的方式来区分。
-
使用读得出来的名字。
-
使用可搜索的名称 : 长名称胜于短名称,搜得到的名称胜于自造编码代写就的名称。单字母名称仅用于端方法中的本地变量。名称长短应与其作用域大小相对应。
-
避免使用编码 : 不要用类型前缀、特定前缀来标记成员。
-
避免思维映射 : 聪明程序员和专业程序员之间的区别在于,专业程序员了解,明确是王道。
-
类名 : 类名和对象名应该是名词或名词短语。
-
方法名 : 方法名应当是动词或动词短语。重载构造器时,使用描述了参数的静态工厂方法名。
-
每个概念对应一个词 : 给每个抽象概念选一个词,并且一以贯之。
-
别使用双关语;
-
使用解决方案领域名称 :
-
使用源自所涉问题领域的名称;
-
添加有意义的语境
-
不要添加没用的语境 : 精确是命名的要点。
取好名字最难的地方在于需要良好的描述技巧和共有文化背景。
第 3 章 函数
-
短小 : 函数的第一规则是要短小,第二条规则是还要更短小。每个函数都只做一件事,且每个函数都依序把读者带到下一个函数,这就是函数应该达到的短小程度。
if 语句、else 语句、 while 语句等,其中的代码块应该只有一行,该行大抵是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。所以,函数的嵌套层级不该多于一层或两层。 -
只做一件事 : 函数应该只做一件事。编写函数是为了把大一些的概念拆分为另一抽象层上的一序列步骤。只做一件事的函数无法被合理地切分为多个区段。
- 每个函数一个抽象层级 : 要确保函数只做一件事,函数中的语句都要在同一抽象层级上。
- switch 语句 : 利用抽象工厂和多态来化解。
- 使用描述性名的称 : 函数越短小、功能越集中,就越便于取个好名字。命名方式要保持一致,使用于模块名一脉相承的短语、名词和动词给函数命名。
- 函数参数 : 最理想的参数数量是零个,尽量避免三个删除。参数与函数名处在不同的抽象层级,它要求你了解目前并不特别重要的细节。尽量不要有标识(比如boolean类型的)参数。
- 无副作用 : 函数最好是无副作用的。输出参数是会被修改的函数的参数;应避免使用输出参数,如果函数必须要修改某种状态,就修改所属对象的状态。
- 分隔指令与询问 : 函数应该修改某对象的状态(指令),或是返回该对象的有关信息(询问)。两样都做会导致混乱。
- 使用异常代替返回错误码 : 抽离 try/catch 语句到另外的函数;错误处理就是一件事;使用异常替代错误码,新异常就可以从异常类派生出来,无须重新编译或重新部署。
- 别重复自己, DRY : 重复可能是软件中一切邪恶的根源。
- 结构化编程 : 每个函数、函数中的每个代码块都应该有一个入口、一个出口。这个规则对于大函数有明显好处。
第 4 章 注释
什么也比不上放置良好的注释来得有用,什么也不会比乱七八糟的注释更有本事搞乱一个模块,什么也不会比陈旧、提供错误信息的注释更有破坏性。
注释最多是一种必须的恶。
如果有足够的表达力,根本就不需要注释。
第 5 章 格式
代码格式关乎沟通,而沟通是专业开发者的头等大事。
每行展现一个表达式或一个语句,每组代码行展示一条完整的思路。这些思路用空白行区隔开来。
变量声明应尽可能靠近其使用位置。
概念相关:概念相关的代码应该放在一起,相关性越强,彼此之间的距离就该越短。
自上向下展示函数的调用依赖顺序。
第 6 章 对象和数据结构
对象把数据隐藏于抽象之后,曝露操作数据的函数。数据结果曝露其数据,没有提供有意义的函数。
过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。
得墨忒尔律(The Law of Demeter)认为,模块不应了解它所操作对象的内部情形。
得墨忒尔律认为,类 C 的方法 f 只应该调用以下对象的方法:
- C ;
- 由 f 创建的对象;
- 作为参数传递给 f 的对象;
- 由 C 的实体变量持有的对象;
- 方法不应调用由任何函数返回的对象的方法。
对象曝露行为,隐藏数据,便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。
数据结构曝露数据,没有明显的行为,便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。
第 7 章 错误处理
- 使用异常而非返回码
- 先写 try-catch-finally 语句
- 使用不可控异常 : 使用可控异常,就得在 catch 语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件中较低层级的修改,都将波及较高层次的签名。
- 给出异常发生的环境说明。
- 依调用者需要定义异常类。
- 定义常规流程 : 创建一个类或配置一个对象,用来处理特例。这样客户代码就不用应付异常行为了,异常行为被封装到特例对象中。
- 别返回 null 值;
- 别传递 null 值;
第 10 章 类
-
类的组织 : 首先是公共静态常量,私有静态变量,私有实体变量,公共函数;公共行数调用的私有工具函数紧随在该公共函数后面。
-
类应该短小 :
- 类的大小以权责(responsibility)多少来衡量。
- 单一职责原则(SRP)认为类或模块应有且只有一条加以修改的理由。
- 内聚:类应该只有少量实体变量,类中的每个方法都应该操作一个或多个实体变量;方法操作是实体变量越多,就越黏聚到类上,如果类中的每个实体变量都被每个方法所使用,则该类具有最大的内聚性。
- 保持内聚性就会得到许多短小的类。
-
为了修改而组织 : 隔离修改。
依赖倒置原则: Dependency Inversion Principle, DIP 。
第 11 章 系统
11.2 将系统的构造与使用分开
软件系统应将起始过程和起始过程之后的运行逻辑分离开,在起始过程中构建应用对象,也会存在互相缠结的依赖关系。
实现方法:
- 分解 main :将全部构造过程搬迁到 main 或称之为 main 的模块中,设计系统的其余部分时,假设所有对象都已正确构造和设置。
- 工厂:
- 依赖注入:在依赖管理情景中,对象不应负责实体化对自身的依赖,反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制的反转。因为初始设置是一种全局问题,这种授权机制通常要么是 main 例程,要么是有特定目的的容器。
11.3 扩容
与物理系统相比软件系统比较独特,它们的架构都可以递增式地增长,只要我们持续将关注面恰当地切分。软件系统短生命周期的本质使这一切变得可行。
面向方面编程(aspect-oriented programming, AOP),是一种恢复横贯式关注面模块化的普适手段。
在 AOP 中,被称为方面(aspect)的模块构造指明了系统中哪些点的行为会以某种一直的方式被修改,从而支持某种特定的场景。这种声明是用某种简洁的声明火编程机制来实现的。
AOP 有时会与其他技术混淆,例如方法拦截和通过代理做的“封包”。AOP 系统的真正价值在于用简洁和模块化的方式指定系统行为。
11.4 Java 代理
代码量和复杂度是代理的两大弱点,创建简洁代码变得很难。代理也没有提供在系统范围内指定执行点的机制,而那正是真正的 AOP 解决方案所必须的。
11.7 测试驱动系统架构
用 POJO 编写应用程序的领域逻辑,在代码层面与架构关注面分离开,就有困难真正地用测试来驱动架构。
最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯 Java(或其他语言)对象实现。不同的领域之间用最不具有侵害性的方面或类方面工具整合起来。
在所有的抽象层级上,意图都应该清晰可辨。只有在编写 POJO 并使用类方面的机制来无损地组合其他关注面时,这种事情才会发生。
无论是设计系统或单独的模块,别忘了使用大概可工作的最简单方案。
第 12 章 迭进
Kent Beck 关于简单设计的四条规则:
- 运行所有测试;
- 不可重复;
- 表达了程序员的意图;
- 尽可能减少类和方法的数量;
- 以上规则按其重要程度排列。
12.2 简单设计规则1:运行所有测试
全面测试并持续通过所有测试的系统,就是可测试的系统。只要系统可测试,就会导向保持类短小且目的单一的设计方案。遵循 SRP 的类,测试起来较为简单。测试编写的越多,就越能持续走向编写教易测试的代码。确保系统完全可测试能帮助创建更好的设计。
测试消除了对清理代码就会破坏代码的恐惧。
12.4 不可重复
重复是拥有良好设计系统的大敌。它代表着额外的工作、额外的风险和额外且不必要的复杂度。
“小规模复用”可大量降低系统复杂性。要想实现大规模复用,必须理解如何实现小规模复用。
12.5 表达力
软件项目的主要成本在于长期维护。
做到有表达力的最重要方式却是尝试。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。