0%

代码整洁之道——系统搭建原则

系统构建

  1. 系统设计
    有专人负责系统层级的结构设计,而非所有开发人员格子编写功能,如此能够在系统层级上保持整洁。
    • 没必要先做大设计,它阻碍改进,心理上会抵制丢弃现有设计,同时架构上的方案选择影响到后续的设计思路。
    • 测试驱动系统架构
    • 善用切面,最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现。不同领域之间用最非侵入性切面或类切面工具整合起来。
  2. 将系统的构造与使用分开
    • 分离构造和使用:分离初始化过程和运行时逻辑,注意在初始化过程中构建应用对象,也会存在互相缠结的依赖关系。
    • 延迟初始化:运行时用到该对象才会去创建,打破了分离构造和使用这个规则。其有一些好处,在真正用到对象之前无需进行无意义的构造。但当其大量出现时,某些全局策略就会在程序中四散分布,缺乏组织性。
    • 模块分离:将所有构造过程在某一模块中实现,其他模块使用过程中默认对象都已经正确构造和设置。
    • 分离手段:
      • 工厂模式:当业务中必须有创建对象过程时,可以使用工厂模式抽离创建过程和业务逻辑。
      • 依赖注入(DI)和控制反转(IoC)

逐步重构

要编写整洁代码,必须先写肮脏代码,然后再清理它。但很多程序在重构会出现无法按照预期工作的情况,为生产造成很大困扰,因而可以测试驱动开发规范,这种方式的核心原则之一是保证系统始终能运行。具体原则如下:

  1. 运行所有测试。系统必须按照期望运行,这是首要因素。遵循有关编写测试并持续运行的简单、明确的规则,系统就会更贴近低耦合度、高内聚度的目标。编写测试引致更好的设计。
  2. 不可重复。重复的功能和代码不应该出现在设计良好的系统中,它代表着额外的工作。如int size()boolean isEmpty()两个方法可以分别实现也可以在isEmpty()中使用size()方法从而实现。
  3. 表达了程序员的意图。代码的其他维护者不会像作者以一样深入要解决的问题,作者把代码写得越清晰,其他人的理解成本越低。
  4. 尽可能减少类和方法的数量。为了保持类和函数短小,我们可能会造出太多细小的类和方法,所以这条规则也主张函数和类的数量要少。我们的目标是在保持函数和类短小的同时,保持整个系统短小精悍。但要记住,这条规则是优先级最低的一条。
  • 通过逐步迭代达到整洁目的,代码需要遵循的原则

系统边界

  1. 使用尚未完成的代码:当上下游代码尚未定义好,我们可以按照自己的期望在系统中先定义这个接口来使用,当接口定义完成后使用Adapter来桥接,这样也会使我们系统本身的代码更加清晰。
  2. 第三方代码
    • 第三方程序包&框架本身追求普适性,能在多个环境中工作,而使用者更希望能满足特定的需求。因此我们应该在第三方包和系统之间创建一个Adapter层用来适配我们的系统用法。
  3. 编写学习性测试,即在测试中对外部代码进行调用并观察其结果的测试。编写学习性测试有以下好处:
    • 通过编写测试来使用、学习第三方API
    • 学习性测试是一种精确试验,帮助我们增进对API的理解,确保第三方程序包按我们想要的方式工作
    • 当第三方包发布了新版本,我们可以运行学习性测试来检测包的行为是否有所改变

单元测试

单元测试使代码可扩展、可维护、可复用。有了测试就不必担心对代码的修改,而没有测试每次修改都可能带来缺陷。

  1. 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)。新增的生产代码仅能在新增的测试,也就是相当于仅能在新增的功能点范围内工作,不能做额外的修改。
  2. 整洁的测试
    • 测试代码和生产代码一样重要,它不是二等公民。我们应该在思考、设计之后再编写测试,并且实时维护,测试代码应该像生产代码一样保持整洁。
    • 使用构造-操作-检验(BUILD-OPERATE-CHECK)的模式,编写明确、简洁、富有表达力的测试。
    • 使用面向特定领域语言来编写测试。
    • 最好每个测试有且只有一个断言来判断测试是否通过。
    • 函数名和要测试的内容相同,符合given-when-then的约定
  3. FIRST
    • F(FAST):快速,测试应该能快速运行。如果测试运行缓慢,开发人员就不会去频繁运行,从而无法暴露问题。
    • I(INDEPENDENT):独立,测试应该可以相互独立运行,某个测试不应该为下一个设定条件。
    • R(REPEATABLE):可重复,测试应该可以在任何环境中重复通过。
    • S(SELF-VALIDATING):自足验证,测试应该有布尔值输出从而判定是否通过,而非通过看日志等方式来确认测试是否通过。
    • T(TIMELY):及时,测试应及时编写。