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