AI 编程工具正在快速进入日常开发流程。现在,开发者可以用自然语言让 AI 生成函数、补全逻辑、解释代码,甚至重构已有模块。过去需要手写的大量样板代码,如今几秒钟就能生成。于是,一个很自然的问题出现了:既然 AI 已经能写代码、读代码、解释代码,我们还需要写注释吗?答案是:需要,但注释的写法必须改变。
过去很多注释是在翻译代码,比如告诉读者这行代码做了什么。在 AI 时代,这类注释的价值会越来越低,因为代码本身、IDE、AI 助手都可以解释表层逻辑,继续写这类注释,只是在给代码增加噪音。真正有价值的注释,不是解释代码做了什么,而是解释代码为什么这样做。
代码可以表达当前实现,但很难完整表达背后的业务规则、历史原因、技术取舍、安全约束和团队共识。这些信息恰恰是未来维护代码时最容易丢失、也最容易导致误改的部分。所以,AI 时代不是不需要注释,而是更需要高质量注释;注释的角色也要从代码翻译器,升级为工程上下文的记录器。一句话概括:代码解释 what,注释解释 why。
很多注释确实没有价值
在讨论要不要写注释之前,必须先承认一个事实:很多代码注释确实是多余的,甚至是有害的。最典型的例子,就是把代码已经表达清楚的动作再翻译一遍:
// i 加 1
i++;
类似的还有这种:
// 遍历用户列表
for (User user : users) {
...
}
这类注释的问题在于,它没有提供任何额外信息。读者直接看代码就能知道它在做什么,注释只是把代码翻译成一句自然语言。这种注释至少有三个问题:第一,它增加阅读噪音,开发者阅读代码时需要在代码和注释之间来回切换,如果注释没有提供新信息,只会降低阅读效率;第二,它容易过期,代码改了,注释可能没改,最终就会出现代码和注释不一致的情况,错误注释比没有注释更危险,因为它会给维护者提供错误上下文;第三,它可能掩盖代码本身的问题,如果一段代码必须依赖大量注释才能看懂,通常优先要做的不是继续补注释,而是检查命名、抽象和结构是否有问题。比如,与其写:
# 判断用户是否可以提现
if u.s == 1 and u.b > x and u.l < 3:
...
不如把意图沉到命名和对象能力里:
if user.can_withdraw(amount):
...
好的命名和清晰的结构,本身就是最重要的注释。它让人和 AI 都能更稳定地理解代码意图,也减少了后续解释成本。所以,反对注释的人并不是完全没有道理,低质量注释确实应该减少,甚至删除;但这并不意味着所有注释都没有价值,真正要反对的不是注释本身,而是没有信息密度的注释。
AI 降低了写代码成本,但没有降低维护成本
AI 时代最大的变化,是代码生成变快了。过去,开发者需要花很多时间写实现细节;现在,AI 可以快速生成一段看起来完整、结构清晰、语法正确的代码,这当然提高了编码效率。但软件工程的难点,从来不只是把代码写出来,真正困难的是:
- 这段代码是否符合业务规则
- 是否覆盖边界场景
- 是否会破坏历史兼容
- 是否满足安全和合规要求
- 是否方便未来维护
- 是否能被团队其他人理解和验证
AI 可以生成实现,但它不一定知道组织内部的历史包袱、业务约束、事故经验和架构取舍。越是依赖 AI 生成代码,越需要把这些上下文沉淀在代码附近,否则未来的人和 AI 都只能从表层实现倒推意图。比如,一段代码里可能故意没有使用并发:
// 这里必须串行处理,因为下游风控服务对单账户有并发限制。
for (User user : users) {
process(user);
}
如果没有这条注释,后来的开发者或者 AI 助手可能会认为这里可以优化成并发处理:
users.parallelStream().forEach(this::process);
从代码表面看,这是一次性能优化。但从系统约束看,它可能引发下游限流、请求拒绝,甚至生产事故。再看合规阈值的例子:
# 50000 是合规审核阈值,调整前需要经过 Legal/Compliance 确认。
if amount > 50000:
require_manual_review()
代码只能告诉我们:超过 50000 要人工审核。但注释告诉我们:这个数字不是随便写的,不是魔法值,也不是可以由开发者单独决定的配置项,而是合规规则的一部分。这就是高质量注释的价值。当 AI 让代码越来越容易生成时,真正稀缺的不是语法能力,而是上下文能力;谁能把业务意图、技术约束和设计原因表达清楚,谁就能让代码在未来更容易被维护。
AI 时代,好注释应该写什么
AI 时代的好注释,不应该解释显而易见的代码行为,而应该解释无法从代码中稳定推导出来的信息。
写为什么,不写是什么
低价值注释通常只是在复述动作:
// 遍历订单
for (Order order : orders) {
...
}
高价值注释则会解释约束:
// 订单必须按创建时间顺序处理,否则账务系统会出现流水顺序不一致。
for (Order order : orders) {
...
}
前者只是重复代码,后者解释了设计约束;一个是在描述动作,一个是在保存原因。
写业务规则来源
很多业务判断并不是纯技术逻辑,而是来自产品规则、运营策略、合规要求或历史约定。注释要尽量说明规则来源,而不是只留下一个看不出依据的判断:
# 新加坡市场超过 50000 的交易需要进入增强审核流程。
# 调整该阈值前必须确认合规要求。
if amount > 50000:
require_manual_review()
这种注释能防止后来的开发者把业务规则误认为普通技术判断。对 AI 编码助手来说,它也能提供更明确的约束,避免生成看似合理、实际越界的修改建议。
写技术取舍
代码里经常存在一些不够优雅的实现,但它们背后可能有真实原因。遇到这种实现,注释最好把取舍直接写出来:
// 这里没有使用 Redis 缓存,因为跨区域失效曾在故障切换时导致脏读。
没有这条注释,后来的人可能会重新引入 Redis 缓存,重复踩坑。这类注释不需要很长,但要把取舍讲清楚:为什么没有选更常见的方案、为什么保留了看起来别扭的实现、什么时候可以重新评估。
写安全和合规约束
在安全、隐私、支付、风控、身份认证等场景中,注释尤其重要,因为这些地方的错误通常不是语法问题,而是边界意识缺失:
// 不要打印完整 payload,其中可能包含用户敏感信息。
logger.info("request received", { requestId });
这类注释不是解释代码行为,而是在提醒维护者:这里有风险边界。AI 编码助手在生成调试代码、错误处理或日志逻辑时,也可能倾向于输出更多上下文。如果代码附近有明确注释,就能降低误生成敏感日志的风险。
写 API 契约和使用限制
公共方法、SDK、框架扩展点、跨团队接口,都值得写清楚文档注释,尤其要说明调用条件、失败方式和使用限制:
/**
* Cancels an order before settlement.
*
* This method can only be called before the order enters SETTLED status.
* Calling it after settlement will return a business error instead of retrying.
*/
public CancelResult cancelOrder(String orderId) {
...
}
这种注释不只是给当前开发者看的,也是给调用方、测试人员、文档生成工具和 AI 编码助手看的。AI 时代,注释的读者变多了;过去主要是:
- 自己
- 同事
- 未来维护者
现在还包括:
- AI 编码助手
- 自动化代码审查工具
- 文档生成工具
- 测试生成工具
因此,注释本质上正在变成一种工程上下文接口。它不只服务阅读,也服务生成、审查、测试和协作。
结论:不是少写注释,而是写更有价值的注释
AI 时代,代码注释不会消失,但会分化。低价值注释会越来越没有存在空间,比如:
// 设置用户名
user.name = name;
这类注释既不能帮助人,也不能真正帮助 AI。但高价值注释会变得更重要,比如:
// 这里保留旧字段是为了兼容 v1 客户端,等 v1 下线后才能删除。
payload.userName = user.name;
这类注释记录了代码之外的信息:兼容策略、迁移计划、删除条件和历史上下文。
判断一条注释是否值得写,可以用一个简单标准:它是否提供了代码本身无法稳定表达的信息?如果答案是否定的,注释大概率可以删除;如果答案是肯定的,注释就有价值。更具体一点,可以问自己四个问题:
- 它是否说明了业务规则或规则来源
- 它是否记录了技术取舍和历史原因
- 它是否提醒了安全、合规、兼容等风险边界
- 它是否帮助未来的人或 AI 避免做出错误优化
最后,可以把代码、注释、测试和文档的关系压缩成一套分工:
- 代码解释
what - 注释解释
why - 测试解释
expected behavior - 文档解释
system context
AI 可以帮助我们写更多代码,但不能替我们承担代码背后的责任。真正成熟的工程实践,不是让代码库充满注释,也不是完全拒绝注释,而是让每一条注释都具备信息密度:少写这段代码做了什么,多写为什么必须这样做。当代码越来越容易生成,真正稀缺的不是代码本身,而是上下文、意图和判断力。
相关阅读
##### FunTester 名片|万粉千文,百无一用