代码整洁之道

303 阅读9分钟

一、整洁代码

糟糕的代码可被形容为沼泽

稍后等于永不——LeBlanc法则

什么是整洁代码?

童子军规——让营地比你来时更干净

二、有意义的命名

  1. 名副其实

    字段含义不需要注释来补充 eg.(可以用函数遮盖魔术数

  2. 避免误导

    避免使用专有名词、相似外形的词等

  3. 有意义的区分

    一般情况下Product、ProductInfo、ProductData没区别

  4. 使用读得出来的名称,而非自造词

  5. 使用可搜索的名称

    WORK_DAYS_OF_WEEK 而非 5

  6. 避免无意义的编码

    类或成员前缀、接口实现等

  7. 避免思维映射——方案术语

  8. 类名是名词或名词短语

    避免manager、Processor、Data、info等

  9. 方法名应是动词或动词短语

  10. 每个概念对应每个词

    controller和manager混用会使人疑惑

  11. 别用双关语

    add 可以是insert,也可以是append

  12. 使用解决方案领域名称

  13. 使用源自所涉问题领域的名称

  14. 添加有意义的语境

    addrFirstName、addrLastName即使单独出现也能理解意思

  15. 不添加没用的语境

三、函数

  1. 短小

    函数的缩进层级不应该多于一层或两层

  2. 只做一件事

    如果函数只做了该函数名下同一抽象层级上的步骤,还是只做了一件事。

    判断:看看是否能再拆出一个函数

  3. 每个函数一个抽象层级

    函数中语句都要处于同一抽象层级上

    自顶向下读代码:向下规则

  4. switch语句

    埋在抽象工厂下或者隐藏于某种继承关系中

  5. 使用描述性名称

  6. 函数参数

    尽量避免三个以上参数

  7. 无副作用

    如果函数必须修改某种状态,那就修改所属对象的状态

  8. 分割指令和询问

    函数要么做什么事、要么回答生命事,二者不可兼得

  9. 使用异常替代返回错误码

    异常处理能使处理错误的代码逻辑从主要代码中分离出来

    • 抽离try/catch语句
    • 错误处理就是一件事——处理错误的函数不应该做其他事情
    • Error依赖磁铁
  10. 重构重复

  11. 结构化编程

    只要函数保持短小,偶尔出现的return、break、continue没有什么坏处,反而更有表达力

四、注释

别给糟糕的代码加注释——重新写吧!

注释总是一种失败,代码在变动,在演化,注释并不总是随之变动。不准确的注释比没注释坏的多。

  1. 注释用代码来阐述

    只需要创建一个描述与注释所言相同的函数即可

  2. 好的注释

    • 法律信息
    • 提供信息的注释——解释了某个抽象方法的返回值
    • 对意图的解释
    • 警示
    • TODO
    • 公共API中的Javadoc
  3. 坏的注释

    • 呐呐自语
    • 多余的注释
    • 误导性注释
    • 循规式注释

五、格式

源文件应向报纸文章那样,名称一目了然,多数短小精悍。

  1. 概念间垂直方向上区隔

  2. 垂直方将上靠近

    紧密相关的代码应该相互靠近

  3. 变量的垂直距离应该尽可能靠近使用位置

  4. 横向格式:100~120左右

六、对象和数据结构

  1. 数据抽象

    数据抽象不仅仅是多一些接口、取值/赋值方法,而是要以最好的方式来呈现某个对象包含的数据,需要严肃的思考。

  2. 对象与数据结构的二分原理

    过程式代码便于在不改动既有数据结构的前提下添加新函数;

    面向对象代码便于在不改动既有函数的前提下添加新的类;

  3. Demeter 定律

    模块不应该了解它所操作对象的内部情况——方法不应调用任何好书返回的对象的方法

    不应链式调用

    隐藏其内部结构:防止当前函数因浏览它不该知道的对象而违反了Demeter定律

  4. 数据传送对象DTO

    只有公共变量,没有函数的类

对象暴露行为,隐藏数据;数据结构暴露数据,隐藏行为。

七、错误处理

  1. 使用异常而非返回码

  2. 先写try-catch-finally语句

    异常在程序中定义了一个范围,try像一个事务,无论try中发生了什么,catch代码都将程序维持在一种持续的状态

  3. 使用不可控异常

    可控异常的代价就是违反开闭原则,必须在catch语句和判处异常间的每个方法签名中声明该异常。(如果在编写一套关键代码库,可控异常会很有用),对于一般应用来说,依赖成本高于收益

  4. 给异常发生的环境说明

    应创建详细的错误消息,并随异常一起传递出去

  5. 依调用者的需要定义异常类

    在定义异常类的时候考虑应如何捕获异常

    有必要的时候可以对第三方的API进行打包,降低对其的依赖

  6. 定义常规流程

    特例模式:创建一个类或配置一个对象,用来处理特例。异常行为被封装到特例对象中

  7. 别返回null

    如果调用第三方API中可能返回null值的方法,可以考虑重新打包,在新方法中抛出异常或者返回特例对象

  8. 避免传递null值

    创建异常或者使用断言

如果将错误处理隔离看待,独立于主逻辑之外,就能大大提升代码的可维护性。

八、边界

  1. 使用第三方代码

    不要将Map在系统中传递,如果使用Map这样的边界接口,就把它保留在类或近亲类中。避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API

  2. 学习性测试?的好处不只是免费

  3. 使用尚不存在的代码

    将未知包括起来,定义自己使用的接口,这样一旦未知接口被定义出来,就使用自己的接口来封装API,同时当API发生变动的情况时,封装就是唯一需要修改的地方

  4. 整洁的边界

    边界代码需要清晰的分割和定义期望的测试,并且避免我们的代码过多的了解第三方代码中的特定信息,依靠你能控制的东西。

    通过代码中少数引用第三方边界接口的位置来管理第三方边界

九、单元测试

  1. TDD三定律

    1. 在编写不能通过的单元测试之前,不可编写生产代码
    2. 只可编写刚好无法通过的单元测试,不能编译也算不过
    3. 只可编写刚好足以通过当前失败测试的生产代码
  2. 保持整洁的测试

    测试代码和生产代码一样重要,需要被思考,被设计

  3. 整洁的测试:可读性、可读性、可读性

    需要明确、简洁、足够的表达力

    测试框架提供的测试形式——构造-操作-检验(BUILD-OPERATE-CHECK)模式

    • 构建测试数据
    • 操作或处理测试数据
    • 校验真实代码的输出结果是否与预期的一致
  4. 每个测试一个断言

  5. FIRST准则

    • Fast 快速
    • Independent 独立
    • Repeatable 可重复
    • Self-Validating 自我验证
    • Timely 及时

十、类

  1. 类的组织

    公共静态变量——私有静态变量——私有实体变量——公共函数

    封装:保持变量和工具函数的私有性,但并不执着于此

  2. 类应该短小

    对于函数,通过计算代码行数衡量大小

    对于类,通过计算权责

    • 单一权责原则:类和模块应有且只有一条加以修改的理由

    • 内聚:方法操作的变量越多,就越黏聚到类上

      保持内聚性能得到很多短小的类

  3. 为了修改而设计

    隔离修改:用接口和抽象类来隔离具体类的细节部分

    依赖倒置DI原则认为:类应当依赖于抽象而不是具体细节

十一、系统

  1. 将系统应用与使用分开

    软件系统应将启动过程与之后的运行逻辑分开,在启动过程中构建应用对象,也会存在相互缠结的依赖关系

    ​ 比如对象构造的代码:应从正常运行的逻辑中分离出来,确保有解决主要依赖问题的全局性一致策略。

    ​ 全局策略如果在程序中四散分布,缺乏模块组织性,通常会有许多重复代码

    • 分解main:将全部构造过程搬到main或者main模块中

    • 工厂:构造能力由工厂持有,而工厂又在main这边

    • 依赖注入:DI和IoC IoC将第二权责从对象中拿出,转移到另一专注于此的对象中,从而遵循了单一权责原则

      延迟初始化:多数DI容器在需要对象前不构造对象、许多DI容器提供调用工厂或构造代理的机制,这种机制可为延迟赋值或类似的优化处理所用

  2. 扩容

    迭代/敏捷:应只去实现今天的用户故事,然后重构,明天再扩展系统,实现新的用户故事。

  3. 切面编程

    • Java代理
    • Java AOP框架
    • AspectJ
  4. 测试驱动系统架构?

    最佳的系统架构由模块化的切面领域该组成,每个切面均用纯Java对象实现。不同领域之间用最小侵害的切面或类切面工具组合起来,这种构架能测试驱动,就像代码一样。

  5. 优化决策

    延迟决策:拥有模块化切面的POJO系统提供的敏捷能力,允许我们基于最新的知识做出优化的、时机刚好的决策

十二、迭进

  1. 运行所有测试

    测试消除了对清理代码会破坏代码的恐惧

    遵循有关编写测试并持续运行测试的简单、明确的规则,系统就会更贴近OO低耦合度、高内聚度的目标。

  2. 不可重复

    小规模复用可大量降低系统复杂性

    模板方法模式是一种移除高层级重复的通用技巧

  3. 表达力

  4. 尽可能短小