Skip to content

代码整洁之道

宏大建筑中最细小的部分,比如关不紧的门,有点没铺平的地板,甚至是凌乱的桌面,都会将整个大局的美丽毁灭殆尽。

前言

代码感 不仅是让我们看到代码的优劣,还予我们借戒规之力化劣为优的攻略

1. 整洁代码

  • 花时间保持代码整洁不但有关效率,还有关生存

  • 我们都曾经说过有朝一日再回头清理,但是稍后等于永不(Later equals never)。

  • 程序员遵从不了解混乱风险的经理的意愿,那也是不专业的做法

  • 制造混乱无助于赶上期限

  • 何为优雅:外表或举止上令人愉悦的优美和雅观,令人愉悦的精致和简单

  • 破窗理论:窗户破损的建筑让人觉得似乎无人关照,于是别人也再不关心,他们放任窗户继续破损,最终自己也参加破坏活动,在外墙上涂鸦,任垃圾堆积。一扇破损的窗户开辟了大厦走向倾颓的道路

  • 整洁的代码如同优美的散文

  • 编写代码的难度,取决于读周边代码的难度,想要干得快,想要轻松写代码,先让代码易读

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

    • 改好一个变量名
    • 拆分一个过长的函数
    • 消除一点重复代码
    • 清理一个嵌套 if 语句

2. 有意义的命名

  • 命名要名副其实,具备准确的语义,避免误导读者
  • 做有意义的区分,避免废话文学和数字拼接命名
  • 使用读的出来的名称
  • 使用可搜索的名称
  • 方法名应当是动词或动词短语
    • Javabean 标准:getXXX,setXXX,isXXX
  • 每个概念对应一个词
    • fetch/retrieve/get
    • controller/manager/driver
  • 避免双关
    • 如果使用 add 代表求和,那就用 insert/append 代表新增
  • 添加有意义的语境,即前缀
    • addrFirstName 胜过 firstName

取好名字的最难的地方在于需要良好的描述技巧和共有文化背景。

3. 函数

  • 最重要的是短小
  • 函数应该做一件事,做好这件事,只做这一件事
  • 每个函数仅一个抽象层级
  • 使用抽象工厂模式替代 switch 语句
  • 使用描述性名称,长而具有描述性的名称要比短而令人费解的名称好
  • 要有足够的特殊的理由函数才能用三个以上参数
    • 当参数足够多的时候,就应该考虑被抽象成一个类
  • 无副作用,即纯函数
  • 避免使用输出参数,即使用参数接收函数返回结果
  • 函数要么做什么事,要么回答什么事,但二者不可得兼
  • 抽离 try/catch 错误处理代码块
    • 函数只干一件事,错误处理就是另一件事
  • 避免重复
    • 软件开发领域的多数创新都是在消灭重复
  • 并不是要从一开始就写出完美的代码
    • 一开始可以冗长且重复,但是需要打磨
    • 分解函数,修改名称,消除重复

4. 注释

注释是一种必须的恶。

别给糟糕的代码写注释,重新写吧。

  • 什么也比不上放置良好的注释来的有用。
  • 什么也不会比乱七八糟得注释更有本事搞乱一个模块
  • 什么也不会比陈旧、提供错误信息得注释更有破坏性

尽管有时需要注释,我们也该多花心思尽量减少注释量

  • 注释不能美化糟糕的代码
  • TODO 注释是必要的,且需定期清理
  • 何为坏注释
    • 作者的喃喃自语,只有作者能看懂
    • 多余的注释,如解释函数做了什么
    • 描述不清晰,甚至有误导性
    • 循规式注释,如要求每个函数都要有 Javadoc
    • 日志式注释,记录每次的更新修改
    • 废话注释
    • 归属和署名,版本控制系统会帮你做这件事
    • 代码注释,如果代码块已不需要那就直接删掉

5. 格式

代码格式不可忽略。

  • 垂直格式
    • 你当然可以用都不超过 500 行的单文件构造出出色的系统
    • 每个空白行都是一条线索,表示出新的独立概念、逻辑块
    • 靠近的代码行暗示了它们之间的紧密关系
      • 变更声明应靠近其使用位置
      • 相关函数,或有直接的调用关系,应该放到一起,且调用者在上面
  • 横向格式
    • 一行大概最多容纳 120 字符
    • 注意水平对齐与缩进
  • 制定团队规则

6. 对象和数据结构

  • 私有变量是不想其他人依赖这些变量
  • 隐藏实现关乎抽象
  • 傻乐着乱加取值器和赋值器是最坏的选择
  • 得墨忒耳律:模块不应了解它所操作对象的内部情形
  • 避免火车失事型代码:final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath()
  • 最精炼的数据结构是数据传送对象即 DTO
  • 对象暴露行为,隐藏数据
  • 数据结构暴露数据,没有明显的行为

7. 错误处理

  • 错误处理如果搞乱了代码逻辑,就是错误的做法
  • 隔离业务逻辑和错误处理
  • 使用不可控异常
    • 可控异常的代价是违反开放/闭合原则,底层修改会波及高层签名
    • 一旦底层函数开始抛出异常就会产生一个贯穿到最高端的修改链来捕获异常
  • 将错误视为一种特例情况,以规避错误处理的逻辑
  • 别返回 null 值,不如抛出异常
    • 或者是空数组
    • 或者是空对象
  • 别传递 null 值

8. 边界

9. 单元测试

  • TDD 三大定律
    • 在编写不能通过的单元测试前,不可编写编写生产代码
    • 只能编写刚好无法通过的单元测试,不能编译也算不通过
    • 只可编写刚好足以通过当前失败测试的生产代码
  • 保持测试整洁
    • 测试代码和生产代码一样重要
    • 它需要被思考,被设计,被照料
  • 整洁测试的三个要素:可读性,可读性和可读性
  • 整洁测试的五条规则:FIRST
    • Fast
    • Independent
    • Repeatable
    • Self-Validating
    • Timely

10. 类

  • 类应该短小
    • 类应该保证单一权责(SRP)
      • 让软件能工作和让软件保持整洁,是截然不同的工作。
    • 内聚性
      • 类中的方法和变量互相依赖,互相结合成为一个逻辑整体
      • 创建极大化内聚类也不可取

17. 味道与启发

代码味道。

  • 注释
    • 不恰当的注释
    • 废弃的,过时的注释
    • 冗余注释
    • 糟糕的注释
    • 注释掉的代码
  • 环境
    • 需要多步才能实现构建
    • 需要多步才能做到的测试
  • 函数
    • 过多的参数
    • 输出参数
    • 标识参数:Boolean 值参数
    • 死函数:永不调用的函数
  • 一般性问题
    • 一个源文件多种语言
    • 明显的行为未被实现、
      • 最小惊异原则
        • 系统的组件(如代码)应按照大多数用户所期望的方式运行
      • 函数或类应该实现其他程序员有理由期待的行为
    • 不正确的边界行为
    • 忽视安全
    • 重复
      • 一次,也只有一次
    • 在错误的抽象层级上的代码
      • 所有较低层级概念放在派生类中
      • 所有较高层级概念放在基类中
    • 基类依赖于派生类
      • 基类应该对派生类一无所知
    • 信息过多
      • 类中方法越少越好
      • 函数知道的变量越少越好
      • 类拥有的实体变量越少越好
    • 死代码
      • 永不执行的代码
    • 垂直分隔
      • 变量和函数应该在靠近被使用的地方定义
    • 前后不一致
      • 每次使用相同命名来持有同类对象
      • 如:response => HttpServletResponse
    • 混淆视听
      • 如没有实现的默认构造器
      • 没用的变量
      • 从不调用的函数
    • 人为耦合
      • 将变量,常量或函数不恰当的放在临时方便的位置
    • 特性依恋
      • 类的方法只应对其所属类中的变量和函数感兴趣,不应该垂青其他类中的变量和函数
      • 但有时特性依恋是有必要的恶行
    • 选择算子参数
      • 不要将函数 boolean 型入参作为计算表达式中的一环
    • 晦涩的意图
      • 代码要有表达力
      • 避免联排表达式,无语义的命名,魔术数
    • 位置错误
      • 将函数变量定义在合适的位置
    • 不恰当的静态方法
      • 如果需要使用静态函数,需要确保不会有多态行为
    • 使用解释性变量
      • 让程序可读的最有力方法之一就是,将计算过程打散成在用有意义的单词命名的变量中放置的中间值
    • 函数名称应该表达其行为
    • 用多态替代 if/else 或 switch/case
    • 遵循标准约定
    • 用常量命名替代魔术数
    • 准确
    • 封装条件
      • 将 if 中的复杂逻辑简化为 bool 判断以表明意图
    • 避免否定性的条件
    • 函数只做一件事
    • 避免掩蔽时序耦合
    • 在较高层级放置可配置数据

推荐阅读

  • 《敏捷软件开发:原则、模式与实践》