Skip to content

可读性与可维护性

可读性和可维护性经常被当作同一个东西谈论,代码评审中"这段代码不好维护"往往实际指的是"这段代码不好读"。但两者回答的是完全不同的问题:可读性回答"我多久能看懂这段代码在做什么",可维护性回答"我改这段代码时要花多大代价、冒多大风险"。

看懂一件事和修改一件事的难度可以差一个数量级。有些代码读起来赏心悦目——命名优雅、结构清晰、注释到位——但改起来牵一发动全身:改一个判断条件要横跨五层抽象、三个接口、两个工厂模式。这就是可读但不可维护的代码。

可读性:理解的成本

可读性衡量的是理解代码的成本,它由几个具体因素决定。命名是第一位的——processDatanormalizeUserInputForValidation 传达的信息量完全不同,前者要求读者继续往下读,后者把意图写在了名字里。控制流复杂度是第二位的——圈复杂度超过 10 的函数,读者需要在大脑中维护的路径分支就超过了工作记忆的容量,必须借助纸笔才能追踪。注释是第三位的——好注释解释"为什么"(业务约束、历史决策),坏注释复述"是什么"(i++ 自增),最坏的注释是撒谎的注释——代码改过而注释没改。

可读性有公认的工程指标:圈复杂度、认知复杂度(SonarQube 的度量,比圈复杂度更贴近人类理解成本)、命名一致性检查。但可读性本质上是一个主观属性——同一段代码对熟悉业务的人和不熟悉业务的人来说可读性完全不同。

可维护性:修改的成本

可维护性衡量的是修改代码的成本和风险,它包含两个被低估的子维度:可写性和可改性。

可写性回答"加一个新功能有多难"。衡量标准不是新功能的代码量,而是为了让新功能落地需要理解和修改的既有代码范围。理想情况下,加一个功能只需要新建文件并注册——插件架构、事件驱动、接口隔离都是在为可写性服务。糟糕的设计下,加一个功能要改十处——这就是可写性差的信号。

可改性回答"改一个现有行为有多难"。这里的关键变量是修改的影响半径。一个修改的影响半径取决于耦合关系:全局状态被多少处读写、一个函数的输出被多少处依赖、一个数据结构的变化会传导到多少个模块。影响半径小的代码,修改前可以精确评估风险;影响半径大的代码,每次修改都是一次赌博。

过度抽象:可读但不可维护的典型

过度抽象是"可读性损害可维护性"的最常见案例。一个被拆分成十二个小类、五个接口、两个工厂的模块,每个局部看起来都清晰优雅——每个类职责单一,每个接口命名考究。但改一个业务行为需要理解全部十二个小类之间的协作关系:数据从 A 流到 B,B 委托 C,C 通过工厂 D 创建 E……读者必须在大脑中重建整个调用链才能动手修改。

这种代码通过了所有可读性检查——低圈复杂度、好命名、单一职责——却在可维护性上全面失败。原因在于抽象引入了间接层,而间接层在理解时是"分层消化"的(每一层都简单),在修改时却是"贯穿理解"的(必须同时理解所有层)。

判断过度抽象的一个实用信号:调试时是否经常需要在多个文件间跳转才能追踪一个业务行为。跳转超过五个文件才能看清一个完整逻辑,抽象就过度了。

可读性与可维护性的冲突点

两者在多数情况下正相关——命名好、结构清晰的代码通常也好改。但在两个维度上存在真实的冲突:

注释与实现的漂移。注释提升可读性,但如果团队不维护注释,注释与实现渐行渐远,最后变成误导——一个写着"处理超时场景"的代码块实际处理的是重试逻辑。误导性的注释比没有注释更损害可维护性,因为读者会基于错误的假设动手修改。工程策略是限制注释用途——只写"为什么"不写"是什么",因为"是什么"的注释最容易漂移。

抽象的收益与代价。抽象提升单点可读性(每个局部更简单),但降低全局可维护性(修改需要贯穿所有抽象层)。抽象的正确粒度由修改频率决定——被频繁修改的代码路径应该保持扁平直白,稳定不变的底层机制才值得抽象封装。

工程实践

将可维护性落到工程实践上,有几个高杠杆的动作。

依赖方向约束是收益最大的单一手段。架构分层(controller → service → repository)的价值不在于"整洁",而在于修改影响半径的可预测性——改 repository 层不可能破坏 controller 层的调用方。工具上可以用架构测试(ArchUnit、Go 的 arch-go)把依赖规则固化为 CI 检查。

修改频率驱动的重构优先级。SonarQube 的代码异味报告按文件列出问题,但更实用的排序是按文件修改频率排序——被每月修改十次的文件值得立即重构,三年没人碰的文件即使有十个代码异味也不值得动。重构的收益与修改频率成正比,这是很多团队忽略的朴素事实。

测试覆盖率在可维护性语境下的真正价值不是"防止 bug",而是"降低修改的心理成本"。改一个被测试覆盖的模块,你知道改坏了 CI 会告诉你;改一个没有测试的模块,每次修改都带着"可能在生产环境爆炸"的不确定性。测试是修改的保险,保险的存在让修改敢于发生。

新成员上手时间是一个常被忽视的可维护性指标。如果一个模块需要两周才能让新成员安全修改,它的隐性成本远高于圈复杂度报表显示的数值。团队轮换时做知识传递的难度,本质上是可维护性的反向度量。