代码简洁之道

“我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。”

​ —— Bjarne,C++语言之父

  1. 简洁代码的必要性
  2. 如何进行命名
    • 名称能够让人理解变量/函数的用途,且易于阅读
    • 避免误导性的词语,让人易于区分
  3. 函数
    • 短小的函数更利于阅读和理解
    • 每个函数只做一件事情
    • 函数名称能够明确指出函数所执行的功能
  4. 注释
    • 好的代码应该能够解释自身行为,注释被认为是糟糕的表现
    • 花时间写注释不如花时间清理那些糟糕的代码
    • 好的注释:提供代码外的信息、说明特殊决定的意图、警示、TODO
  5. 格式
    • 概述到详细细节应该按顺序展开(从上到下,C编码习惯是从下到上)
    • 使用空白行将不同功能块隔开
    • 定义变量应该靠近使用的地方(编码规范要求函数头部定义变量,在靠近使用的地方初始化)
    • 保持合适的宽度,建议在120个字符之内,且使用空格将变量、操作符、参数等隔开
    • 水平方向的对其且保持合适的缩进
  6. 对象和数据结构域
    • 使用抽象接口提供数据的操作,且不暴露数据细节(公共库接口)
    • 面向过程式编码难以添加新的数据结构,因为需要修订现有函数;面向对象式编码难以添加新函数,因为需要修改现有所有类
    • 得墨忒耳定律:每个模块对其它模块只能有有限的了解(如付款时拿出钱包里的钱给服务员,而不是将钱包给服务员)
  7. 错误处理(Java)
    • 被调用函数处理异常时应该抛出异常而不是返回错误码,调用者容易忽略处理错误结果(处理有问题不return直接coredump,不适用)
    • 错误处理返回需要能够让调用者明确错误原因
  8. 单元测试
    • TDD三定律
      1. 在编写不能通过的单元测试前,不可编写生产代码
      2. 只可编写刚好无法通过的单元测试,不能编译也算不过
      3. 只可编写刚好足以通过当前失败测试的生产代码
    • 好的测试代码能够保证生产代码的可扩展、可维护、可复用,
    • 测试代码需要保证可读性(明确、整洁、表达力)
    • 保证测试用例的颗粒性更容易阅读,且能够在出现问题时快速定位根因
    • FIRST
      1. 快速Fast,用例能够快速执行
      2. 独立Independent,用例保证独立性,当前用例执行结果不会影响下个用例
      3. 可重复Repeatable,用例能够在多种环境中重复使用
      4. 自足验证Self-Validating,用例需要输出结果用来明确用例执行结果
      5. 及时Timely,在编写生产代码之前输出测试用例
  9. 类(Java)
    • 变量列表→公共静态常量→私有静态变量→私有实体变量
    • 类应该短小,明确职责
    • SRP,单一职责原则,一个类或模块只负责完成一个功能
  10. 系统
    • 将系统的构造(main)和使用(proto)分开
    • 迭代和增量敏捷,系统都是从小到大逐渐拓展的,一个设计时就考虑到所有场景的系统是神话
    • 独立简洁,意图明确
  11. 迭进
    • 软件的“简单设计”原则
      1. 运行所有测试(系统可测试且持续输出测试用例可以引导更好的设计,低耦合度、高内聚度)
      2. 不可重复
      3. 表达程序员意图
      4. 尽可能减少类和方法的数量(2~4,新增代码时考虑系统性能是否降低,降低时及时重构代码,良好的测试用例能够保证重构后代码的不可预测性,降低重构代码的恐惧)
    • 重复代码会导致额外的工作、风险、复杂度,提倡“小规模复用”(执行简单单一功能的接口,如常用的返回结果检查接口)
    • 简洁清晰的代码能够降低后来人理解代码的时间,减低维护成本
  12. 并发编程
    • 解耦目的(做什么)和时机(什么时候做)
    • 多个线程并发操作同一数据对象可能会导致结果不可预测
    • 并发防御原则
      1. SRP→分离并发和其它逻辑代码
      2. 限制对共享数据的访问(加锁)
      3. 共享数据只读,单线程更新共享数据
      4. 基础定义:限定资源、互斥、线程饥饿、死锁、活锁(活锁:持续等待资源;死锁:永远无法获得资源)
  13. 逐步改进(以Java代码为例讲述优化代码的过程)
    • 先写肮脏代码,再清理,最后达到整洁代码,避免代码“能工作”就认为任务完成
    • 以测试驱动开发,保证代码在不断修改中维持功能不变且可运行
  14. JUnit内幕(Java框架)
  15. 重构SerialDate(代码规范清单)
    1. 注释:及时删除/修订不恰当注释、废弃注释、冗余注释、注释掉的代码
    2. 环境:系统功能由单个简单命令完成、单个命令可运行全部单元测试
    3. 函数:避免过多参数、输出参数、死参数
    4. 一般性问题:
      1. 明显行为未被实现
      2. 错误的边界行为
      3. 忽视安全
      4. 抽象重复代码
      5. 避免一个函数处理过多信息
      6. 避免死代码(永远不会被执行)
      7. 缩短垂直距离,变量和初次使用位置距离尽可能短
      8. 保持变量名称与用法的对应关系在任意位置保持一致
      9. 删除无用的变量、函数、注释,保持整洁
      10. 使用解释性变量,函数名称表达实现功能
      11. 理解算法
      12. 使用可理解的命名常量替代魔数
      13. 使用肯定式的表达式替代否定句式
      14. 函数只做一件事
      15. 避免传递浏览,A和B交互,A只了解B的信息,B和C交互,B只了解C的信息
    5. 名称
      1. 使用描述性名称,使人一目了然
      2. 使用标准、无歧义的命名
    6. 测试
      1. 保证足够的覆盖面
      2. 使用覆盖率
      3. 不要忽略小测试
      4. 测试边界条件
      5. 测试应该快速

代码简洁之道
https://sslin.online/2023/02/14/代码简洁之道/
作者
sslin
发布于
2023年2月14日
许可协议