如何提高代码的可读性?

393 阅读6分钟

  适合自己的才是最好的。以下内容参考《编写可读代码的艺术》一书。

  可读性是衡量代码质量的一个核心指标,追求减少代码行数固然重要,更关键的是要减少阅读者理解代码所花费的时间。

1. 让命名承载信息

命名应当直观表达其用途,使阅读者见名知意。优秀的命名等同于一条注释,可以传递大量信息。

  • 使用领域专业术语:如加密领域里的 encrypt, decrypt, signature 等。
  • 避免口语化:比如使用 init 代替 start,register 代替 new 等。
  • 使用行业公认缩写:如公钥 PK 、私钥 SK。
  • 使用有意义的代称:尽量避免直接使用 data、result 等泛化词汇,可以增加上下文信息,使名称更具体,如 userInfo、validatedResult。
  • 变量名中包含单位或格式:如使用 timeoutInSeconds 代替 timeout,十六进制编码字符串 hexString。
  • 使用前后缀区分不同层级数据:如 _ 前缀用于局部变量,与全局变量区分。
  • 避免语义重复:如 convertToString 可简化为 toString。
  • 统一风格:一般驼峰用于变量名和方法名,下划线用于常量名或文件名、表名等特定属性。
  • 合理控制长度:小作用域内,变量名可以使用简写,如 string 简写为 str, document 简写为 doc。
  • 消除歧义:确保名称含义清晰,如 min 和 max 包含极限值,begin 和 end 适合范围,表示起点与终点,start 和 finish 适合过程,表示启动和结束。
  • 明确布尔值:如使用 is、has、need、can、use 等前缀,避免使用双重否定,如 disable = false。
1.1. 命名规范建议
  • 类名:多为名词,更多的是体现其职责,如 UserController、EncryptHelper、DataUtils 等。
  • 接口名:多为形容词或描述功能的词,更多是体现其能力,如 Serializable、 Runnable、Callable 等。
  • 方法名:多为动词或动宾结构,清晰表达方法的操作,如 CURD 命名、validateData、isAvailable 等。

2. 审美与格式化

合理使用留白、对齐、换行、分组,让代码逻辑有一定的结构感。

  • 保持风格一致:如限制行长为 100 个字符,避免过长的代码行导致阅读困难。
  • 有意义的顺序:参数、变量按重要性统一排列,避免同样参数此处是 ABC,在另一个地方是 CBA 。
  • 按逻辑相关性分组:用空行分隔代码块,使结构清晰。

3. 编写有效注释

注释不仅仅是描述代码的作用,更多的是帮助阅读者了解设计意图。

  • 避免冗余注释:易懂的代码无需额外注释。
  • 避免不佳命名的注释:替换不清晰的命名,而非用注释弥补。
  • 记录思考过程:特别是使用了设计模式和复杂算法的地方。
  • 标记任务:使用 TODO-待完成, FIXME-有问题待修复, XXX-待改进等标识标记代码。
  • 高层次注释:概述类间交互和数据流向。
  • 摘要性注释:非复杂逻辑,只需表达代码目的而非细节。
  • 更新注释:过期或错误的注释,比没有注释更麻烦。

4. 简化控制流程

过多的条件判断、循环、嵌套会让代码变得混乱,应保持流程简洁、线性。

  • 逻辑顺序:一般条件语句中左侧为变量,右侧为常量,如 length > 0 比 0 < length 更自然。
  • 正向逻辑优先:正向判断是大多数业务逻辑的主流写法,如非空值执行流程,空值处理则作为补充。
  • 三元表达式:适合简单的判断逻辑,如果出现三元表达式嵌套,建议转成 if-else 。
  • 避免使用 do-while: 因为至少会执行一次,通常情况下,判断条件应该出现在执行代码之前。
  • 拆分复杂表达式:使用解释性变量,将复杂的表达式分解为小块。
  • 布尔表达式优化:根据需要取反或合并表达式。
  • 局部变量在使用前声明:避免顶部统一声明多个变量,导致阅读障碍。
4.1. 关于判断是否为 null 的写法

到底是 user == null 还是 null == user ?

C 语言中使用 null == user ,是为了避免遗漏一个 = 变成赋值 user = null,但在 Java 中不需要这种形式,其一现在的开发工具或编译器可以检查出这种语法错误并提示,其二 user == null 更符合阅读逻辑,因为我们想知道 user 是否为 null,而不是 null 是否等于 user。

5. 命名模式总结

除了常规的 Controller、Service、Mapper、Repository 命名外,下面这些借鉴了 Spring、Netty 等开源代码的命名方式。

类型含义适用场景示例
Bootstrap / Starter启动器程序或模块启动ServerBootstrap
Processor处理器用于处理特定功能或逻辑MessageProcessor
Manager管理器用于对象生命周期或资源管理TransactionManager
Provider提供者用于提供某类服务或接口实现ServiceProvider
Register注册器用于注册资源或服务EventRegister
Helper辅助类封装简单功能StringHelper
Context上下文参数传递容器ApplicationContext
Callback回调异步任务后的操作RetryCallback
Parser解析器数据解析JsonParser
Validator校验器用于检查数据的合法性DataValidator
Formatter格式化器数据格式化DateFormatter
Converter转换器类型转换NumberConverter
Selector选择器选择合适的对象或数据FileSelector
Generator生成器用于生成特定对象或数据TokenGenerator
Initializer初始化器用于初始化,通常在加载前执行BeanInitializer
Pool对象池资源的复用管理ThreadPool
Action动作表示一种可执行的动作UserAction
Command命令封装操作或请求LoginCommand
Component组件可独立使用的单元UserComponent
Cache缓存保存数据的缓存类MemoryCache
Metric度量用于性能或数据统计CacheMetric
Template模板常用于定义操作步骤JdbcTemplate
Builder构建器用于构建复杂对象StringBuilder
Factory工厂用于创建对象的类ConnectionFactory
Strategy策略实现特定策略的算法接口或类SortStrategy
Task任务需要调度执行的单元ScheduledTask
Trigger触发器调度或规则控制的触发条件CronTrigger
Scheduler调度器控制任务调度的模块TaskScheduler
Event事件用于通知系统状态发生变化的事件UserEvent
Listener监听器用于监听事件EventListener
Publisher发布者用于发布消息或事件EventPublisher
Subscriber订阅者用于接收发布者发送的数据MessageSubscriber
Handler事件处理器用于处理接收到的消息或事件NotificationHandler