1. 不同类型的注释
在 Java 中,我们常使用 Javadoc 来编写 API 文档。类似的概念在 PHP 中称为 Docblock,在 C# 中则被称为 XML 文档注释。这类注释的主要目的是:
✅ 向使用者说明 API 的使用方式
✅ 描述输入输出格式、预期异常等关键信息
✅ 保证文档清晰、完整、无歧义
这类注释是面向 API 使用者的,而我们这篇文章更关注面向开发者的注释。这类注释通常用于:
- 解释某些复杂逻辑或实现思路
- 标记待修复的问题或未来需要改进的地方
- 说明代码背后的业务逻辑或决策原因
2. 注释的目标
写注释时,我们要意识到目标读者可能是谁:
- 项目生命周期中会有很多人参与
- 包括初级开发者、高级开发者、不同文化背景的人
理想情况下,代码应该是自解释的,不需要注释也能让人理解。但在某些情况下,代码本身无法完全表达意图。比如下面这个正则表达式:
// 匹配荷兰邮编,例如 '1974 XA' 或 '4844RA'
Pattern DUTCH_ZIPCODE = Pattern.compile("[1-9][0-9]{3} ?[A-Z]{2}");
对于熟悉正则表达式或荷兰邮编格式的人来说,这行代码可能已经很清晰了。但如果是刚入职的法国新人,这行注释就非常有帮助。
3. 注释的缺点
虽然注释在写的时候可能是正确的,但它们很容易过时:
❌ 代码变更后,注释没更新
❌ 注释与代码分离,甚至被插在代码中间
❌ 注释变成“孤儿”,没人维护
另外,有些项目强制要求写注释,但写出的注释毫无信息量,反而让人习惯性忽略它。最终导致注释失效,甚至误导他人。
✅ 所以,我们应该优先写自解释代码,注释只能是例外情况。
4. 不恰当的注释用法
4.1. 重复明显的信息
比如下面这些注释就毫无意义:
// numeric session-id
int sessionId = 0;
// expiry date
Date expiry = new Date();
这些信息已经通过变量名表达清楚了,加注释反而多余。更糟糕的是下面这个例子:
// numeric session-id
int sessionTimeoutInSeconds = 3600;
这个注释是从别的地方复制过来的,没有更新,反而会造成误解。
4.2. 用注释解释难懂的代码
与其写注释解释代码,不如直接把代码写得更清晰。例如:
int timeout = 5; // timeout in seconds
不如改写为:
int timeoutInSeconds = 5;
这样变量名本身就表达了单位,注释自然就多余了。
再看一个 if 判断的例子:
// if during work hours
if (currentTime.hours >= 9 && currentTime.hours <= 17)
可以重构为:
if (isDuringWorkingHours(currentTime))
这样不仅更清晰,也省去了注释。
4.3. 用注释记录版本变更
有些人喜欢在代码中加上作者、日期、历史记录:
// MvW 2020-08-20 Added method to validate working hours
function isDuringWorkingHours() {
...
}
这种做法已经过时了。我们有 Git、SVN 等版本控制系统,这些信息应该由 VCS 来记录。注释中不应该保留历史信息,否则容易造成混乱。
✅ 正确做法:删除旧代码,使用清晰的 commit message 记录变更。
5. 合理的注释使用场景
尽管我们强调少用注释,但在某些情况下还是很有必要的:
5.1. 法律或版权信息
例如,有些项目要求每个文件开头都加上版权声明:
/*
* Copyright (c) 2024 Acme Inc. All rights reserved.
*/
这类注释不涉及业务逻辑,不会随着代码变更而变化,适合保留。
5.2. 标记待办事项或已知缺陷
使用 TODO
或 FIXME
注释可以标记当前需要处理但尚未完成的任务:
// FIXME CS-122 该方法在闰年会出错
function isAfterWorkingHours() {
...
}
这些注释能帮助团队快速识别待办事项。如果项目使用 JIRA 等任务管理系统,建议将注释与具体任务编号绑定。
5.3. 解释非显而易见的代码逻辑
对于正则表达式、日期解析、位运算等复杂逻辑,适当注释可以帮助理解:
// 匹配荷兰邮编,例如 '1974 XA' 或 '4844RA'
Pattern DUTCH_ZIPCODE = Pattern.compile("[1-9][0-9]{3} ?[A-Z]{2}");
这类注释尤其在 Code Review 时非常有用。
5.4. 解释“为什么”这么做
有时候代码看起来“奇怪”,但背后有特定原因。这时注释就非常关键:
int resultCode = ...;
boolean isSucess = resultCode == 200;
boolean isTemporaryError = resultCode == 503 || resultCode == 504;
// 有时会收到临时错误。
// 但在我们研究的所有情况下,结果都已成功保存。
// 因此在这种情况下我们返回 true
return (isSucess || isTemporaryError);
或者:
// 不要改用 try-with-resources,因为会自动关闭 socket。
// 该 socket 每 15 分钟需要使用一次,所以不能关闭。
// 否则问题不会立即暴露,但会导致应用崩溃
try {
... 打开 socket
} catch (IOException e) {
... 错误处理
}
这些注释不是解释“做了什么”,而是解释“为什么这么做”,避免他人误改代码。
6. 总结
注释不是越多越好,也不是越少越好。关键在于:
✅ 注释要有信息量,能帮助理解代码
✅ 避免重复、误导、过时的注释
✅ 优先写自解释代码,注释是例外情况
✅ 使用 TODO/FIXME 标记待办事项
✅ 用注释说明“为什么”而不是“做了什么”
记住一句话:注释是代码的补充,不是替代品。