系统构建
- 系统设计
有专人负责系统层级的结构设计,而非所有开发人员格子编写功能,如此能够在系统层级上保持整洁。- 没必要先做大设计,它阻碍改进,心理上会抵制丢弃现有设计,同时架构上的方案选择影响到后续的设计思路。
- 测试驱动系统架构
- 善用切面,最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现。不同领域之间用最非侵入性切面或类切面工具整合起来。
- 将系统的构造与使用分开
- 分离构造和使用:分离初始化过程和运行时逻辑,注意在初始化过程中构建应用对象,也会存在互相缠结的依赖关系。
- 延迟初始化:运行时用到该对象才会去创建,打破了分离构造和使用这个规则。其有一些好处,在真正用到对象之前无需进行无意义的构造。但当其大量出现时,某些全局策略就会在程序中四散分布,缺乏组织性。
- 模块分离:将所有构造过程在某一模块中实现,其他模块使用过程中默认对象都已经正确构造和设置。
- 分离手段:
- 工厂模式:当业务中必须有创建对象过程时,可以使用工厂模式抽离创建过程和业务逻辑。
- 依赖注入(DI)和控制反转(IoC)
逐步重构
要编写整洁代码,必须先写肮脏代码,然后再清理它。但很多程序在重构会出现无法按照预期工作的情况,为生产造成很大困扰,因而可以测试驱动开发规范,这种方式的核心原则之一是保证系统始终能运行。具体原则如下:
- 运行所有测试。系统必须按照期望运行,这是首要因素。遵循有关编写测试并持续运行的简单、明确的规则,系统就会更贴近低耦合度、高内聚度的目标。编写测试引致更好的设计。
- 不可重复。重复的功能和代码不应该出现在设计良好的系统中,它代表着额外的工作。如
int size()和boolean isEmpty()两个方法可以分别实现也可以在isEmpty()中使用size()方法从而实现。 - 表达了程序员的意图。代码的其他维护者不会像作者以一样深入要解决的问题,作者把代码写得越清晰,其他人的理解成本越低。
- 尽可能减少类和方法的数量。为了保持类和函数短小,我们可能会造出太多细小的类和方法,所以这条规则也主张函数和类的数量要少。我们的目标是在保持函数和类短小的同时,保持整个系统短小精悍。但要记住,这条规则是优先级最低的一条。
- 通过逐步迭代达到整洁目的,代码需要遵循的原则
系统边界
- 使用尚未完成的代码:当上下游代码尚未定义好,我们可以按照自己的期望在系统中先定义这个接口来使用,当接口定义完成后使用Adapter来桥接,这样也会使我们系统本身的代码更加清晰。
- 第三方代码
- 第三方程序包&框架本身追求普适性,能在多个环境中工作,而使用者更希望能满足特定的需求。因此我们应该在第三方包和系统之间创建一个Adapter层用来适配我们的系统用法。
- 编写学习性测试,即在测试中对外部代码进行调用并观察其结果的测试。编写学习性测试有以下好处:
- 通过编写测试来使用、学习第三方API
- 学习性测试是一种精确试验,帮助我们增进对API的理解,确保第三方程序包按我们想要的方式工作
- 当第三方包发布了新版本,我们可以运行学习性测试来检测包的行为是否有所改变
单元测试
单元测试使代码可扩展、可维护、可复用。有了测试就不必担心对代码的修改,而没有测试每次修改都可能带来缺陷。
- TDD(测试驱动开发(Test-Driven Development))三定律
- 编写生产代码之前必须先写一个能使当前代码无法通过的单元测试(You must write a failing unit test before you write production code)。为本次要增加的功能点编写一个测试,当前版本的代码尚不具有这个功能,所以理应无法通过测试。
- 只能编写刚好无法通过的单元测试,不能编译也算不过(You must stop writing that unit test as soon as it fails; and not compiling is failing)。新增的测试应该仅覆盖本次需要新增的功能点。
- 只可编写刚好足以通过当前失败测试的生产代码(You must stop writing production code as soon as the currently failing test passes)。新增的生产代码仅能在新增的测试,也就是相当于仅能在新增的功能点范围内工作,不能做额外的修改。
- 整洁的测试
- 测试代码和生产代码一样重要,它不是二等公民。我们应该在思考、设计之后再编写测试,并且实时维护,测试代码应该像生产代码一样保持整洁。
- 使用构造-操作-检验(BUILD-OPERATE-CHECK)的模式,编写明确、简洁、富有表达力的测试。
- 使用面向特定领域语言来编写测试。
- 最好每个测试有且只有一个断言来判断测试是否通过。
- 函数名和要测试的内容相同,符合given-when-then的约定
- FIRST
- F(FAST):快速,测试应该能快速运行。如果测试运行缓慢,开发人员就不会去频繁运行,从而无法暴露问题。
- I(INDEPENDENT):独立,测试应该可以相互独立运行,某个测试不应该为下一个设定条件。
- R(REPEATABLE):可重复,测试应该可以在任何环境中重复通过。
- S(SELF-VALIDATING):自足验证,测试应该有布尔值输出从而判定是否通过,而非通过看日志等方式来确认测试是否通过。
- T(TIMELY):及时,测试应及时编写。