1-3-1-注释哲学
1.3.1 注释哲学:注释不是说明,而是承认
引言
关于注释,有两种极端观点:
极端1:"好的代码不需要注释,代码应该自解释。"
极端2:"代码应该写详细的注释,每一行都要解释。"
真相在中间:代码应该尽可能自解释,但注释用来解释代码无法表达的东西。
更深层次地理解:注释不是在说明代码做了什么,而是在承认代码的局限性。
注释的三种类型
1. 无用的注释(Noise)
这种注释只是重复代码,没有增加任何信息:
# 获取用户
user = get_user(id)
# 如果用户存在
if user:
# 返回用户名
return user.name
问题:这些注释纯粹是噪音。代码本身已经清楚表达了意图。
删掉它们,代码更清晰:
user = get_user(id)
if user:
return user.name
2. 道歉的注释(Apology)
这种注释是在为糟糕的代码道歉:
# 这段代码有点乱,但能用
def calc(a, b, c, d):
return (a + b) * c - d if d > 0 else (a - b) * c + d
# HACK: 这里有个临时的解决方案
if user.id in [1, 2, 3]:
return 'admin'
# TODO: 这里需要重构
def process_data(data):
# 500 行混乱的代码
这些注释是在"承认"问题,但问题在于:
与其写注释道歉,不如直接修复代码。
正确做法:
# 不要写道歉的注释
# 重构代码,让它不需要道歉
def calculate_final_price(base, tax, discount, coupon):
price_after_tax = base * (1 + tax)
if coupon > 0:
return price_after_tax - coupon - discount
else:
return price_after_tax + discount
# 或者,如果真的是临时方案,至少说明替代方案和时间表
def get_user_role(user):
# 临时方案:硬编码管理员ID
# 计划:2024-Q1 迁移到 RBAC 系统(负责人:Alice)
# 跟踪:issue #1234
if user.id in [1, 2, 3]:
return 'admin'
return 'user'
3. 有价值的注释(Insight)
这种注释解释代码无法表达的东西:
3.1 解释"为什么"
def calculate_shipping_cost(weight, distance):
# 使用简化的计算公式而非精确的物流模型
# 原因:
# 1. 精确模型需要实时调用第三方API,响应慢且不稳定
# 2. 根据历史数据,简化公式的误差 < 5%,业务可接受
# 3. 用户体验优先级高于精确性
base_cost = weight * 0.5 + distance * 0.1
return round(base_cost, 2)
这个注释有价值,因为它解释了:
- 为什么选择这种方案
- 考虑了哪些替代方案
- 做了什么权衡
3.2 解释业务背景
def apply_discount(order_total):
# 根据财务部门要求,折扣金额必须保留2位小数
# 避免出现 0.1 + 0.2 = 0.30000000000000004 的浮点数精度问题
# 历史教训:2023-03-15 因精度问题导致财务对账失败
discount = Decimal(str(order_total * 0.1))
return discount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
这个注释有价值,因为它解释了:
- 业务规则的来源(财务部门要求)
- 技术原因(浮点数精度问题)
- 历史教训(曾经出过问题)
3.3 解释非显而易见的边界情况
def parse_date(date_string):
# 注意:用户可能输入多种日期格式
# - 2024-01-15
# - 01/15/2024
# - Jan 15, 2024
# 优先匹配 ISO 格式,因为它无歧义
for fmt in ['%Y-%m-%d', '%m/%d/%Y', '%b %d, %Y']:
try:
return datetime.strptime(date_string, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期:{date_string}")
这个注释有价值,因为它解释了:
- 为什么要尝试多种格式
- 优先级的选择依据
3.4 解释算法或复杂逻辑
def calculate_next_billing_date(start_date, subscription_type):
# 计费逻辑:
# - 月度订阅:下个月的同一天(如1月15日 -> 2月15日)
# - 特殊情况:如果当前是1月31日,下个月是2月,则使用2月28日(非闰年)或2月29日(闰年)
# - 年度订阅:下一年的同一天
#
# 这个逻辑参考了 Stripe 的计费规则
# 文档:https://stripe.com/docs/billing/subscriptions/billing-cycle
if subscription_type == 'monthly':
# 使用 relativedelta 处理月份边界问题
next_date = start_date + relativedelta(months=1)
return next_date
elif subscription_type == 'yearly':
return start_date + relativedelta(years=1)
这个注释有价值,因为它解释了:
- 复杂的业务规则
- 边界情况的处理
- 参考资料
3.5 警告陷阱
def delete_user(user_id):
# 警告:这是软删除,不会真正删除数据库记录
# 如果需要硬删除(GDPR 合规),使用 permanently_delete_user()
# 注意:硬删除会级联删除用户的所有数据,无法恢复
user = User.query.get(user_id)
user.deleted_at = datetime.now()
db.session.commit()
这个注释有价值,因为它警告了:
- 函数行为与名字可能不符(叫 delete 但其实是软删除)
- 真正删除的替代方法
- 潜在风险
注释是承认什么
承认1:代码无法完全自解释
# 这段代码看起来很简单
sleep(1000)
# 但为什么要 sleep 1秒?
# 需要注释说明
# 等待 1 秒,让第三方 API 的速率限制窗口重置
# API 限制:每秒最多 1 次请求
# 文档:https://api.example.com/docs/rate-limiting
sleep(1)
承认:仅仅看代码,无法知道 sleep 的原因。
承认2:业务规则复杂,代码无法表达全部含义
def calculate_tax(amount, user):
# 税率根据用户所在州决定
# 特殊规则:
# - 德州(TX):无州税
# - 加州(CA):7.25%
# - 特殊情况:非营利组织免税(根据 IRS 501(c)(3) 规定)
#
# 参考:https://www.irs.gov/charities-non-profits
if user.organization_type == 'non_profit':
return 0
tax_rates = {
'TX': 0.0,
'CA': 0.0725,
# ... 其他州
}
return amount * tax_rates.get(user.state, 0.05) # 默认 5%
承认:税法很复杂,代码只是实现,注释提供业务背景。
承认3:技术限制或历史原因
def hash_password(password):
# 使用 bcrypt 而非更快的 scrypt
# 原因:遗留系统兼容性
# 历史:2015年系统上线时,Python 的 scrypt 支持不稳定
# 计划:2024-Q3 迁移到 argon2(跟踪:issue #2345)
return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
承认:技术选型不是最优的,但有历史原因。
承认4:性能优化做了牺牲可读性
def find_duplicates(numbers):
# 使用集合而非朴素的双重循环
# 时间复杂度:O(n) vs O(n^2)
# 空间复杂度:O(n) vs O(1)
# 权衡:用空间换时间,因为输入规模可能很大(> 10万)
seen = set()
duplicates = set()
for num in numbers:
if num in seen:
duplicates.add(num)
else:
seen.add(num)
return list(duplicates)
承认:优化后的代码不如朴素版本直观,注释解释了原因。
何时写注释,何时不写
不需要注释的情况
1. 代码意图很清楚
# 不需要:注释重复代码
# 计算购物车总价
total = sum(item.price * item.quantity for item in cart.items)
# 需要:如果计算有特殊规则
# 计算购物车总价(不包含免费赠品)
total = sum(
item.price * item.quantity
for item in cart.items
if not item.is_free_gift
)
2. 函数名已经说明了一切
# 不需要注释
def send_welcome_email(user):
# 发送欢迎邮件 ← 多余
...
# 需要注释:如果有特殊行为
def send_welcome_email(user):
# 注意:对于企业用户,使用企业邮件模板
# 并抄送给账户经理
if user.is_enterprise:
template = 'enterprise_welcome'
cc = user.account_manager.email
else:
template = 'standard_welcome'
cc = None
email_service.send(user.email, template, cc=cc)
需要注释的情况
1. 正则表达式
# 需要:正则表达式很难读
# 匹配邮箱格式:
# - 任意字符(不含空格和@)
# - @
# - 任意字符(不含空格和@)
# - .
# - 2-6个字母(顶级域名)
email_regex = r'^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,6}$'
2. 魔法数字
# 不好
sleep(86400)
# 好
# 等待 24 小时(86400 秒)
SECONDS_PER_DAY = 86400
sleep(SECONDS_PER_DAY)
# 或者
sleep(24 * 60 * 60) # 24 小时
3. 非直观的算法
def find_median(numbers):
# 使用快速选择算法(QuickSelect)
# 时间复杂度:平均 O(n),最坏 O(n^2)
# 优于排序法的 O(n log n)
#
# 参考:https://en.wikipedia.org/wiki/Quickselect
if not numbers:
return None
sorted_numbers = sorted(numbers)
n = len(sorted_numbers)
if n % 2 == 1:
return sorted_numbers[n // 2]
else:
mid1 = sorted_numbers[n // 2 - 1]
mid2 = sorted_numbers[n // 2]
return (mid1 + mid2) / 2
对使用 AI 的程序员的建议
AI 生成的代码往往缺少注释,或者注释质量不高。
AI 生成注释的常见问题
问题1:重复代码的注释
# AI 可能生成
# 创建一个新用户
user = User(name=name, email=email)
# 保存到数据库
db.save(user)
# 返回用户
return user
改进:删除这些噪音注释。
问题2:缺少"为什么"的注释
# AI 可能生成
def get_price(product, user):
if user.is_premium:
return product.price * 0.9
return product.price
改进:添加业务背景
def get_price(product, user):
# 高级会员享受 10% 折扣
# 业务规则:2024-01-01 起生效
# 参考:产品文档 PRD-2024-001
if user.is_premium:
return product.price * 0.9
return product.price
如何让 AI 生成有价值的注释
方法1:要求解释"为什么"
请为这段代码添加注释,重点解释:
1. 为什么选择这种算法或方案
2. 有哪些边界情况需要注意
3. 性能考虑
4. 业务背景
不要写重复代码的注释。
方法2:要求添加参考资料
这段代码实现了一个复杂的算法,请添加注释:
1. 算法的原理
2. 时间和空间复杂度
3. 参考资料链接
注释的维护
注释也会过时
# 这个注释写于 2020 年
# 临时方案:使用硬编码的API密钥
# 计划:2020-Q4 迁移到环境变量
API_KEY = "hardcoded_key_123"
# 现在是 2024 年,代码还在,计划早已被遗忘
# 新人看到注释,以为"2020-Q4会改",但不知道已经 4 年了
教训:
- 如果临时方案没有被替换,更新注释或删除"计划"部分
- 定期审查注释,确保它们仍然准确
注释的版本控制
不好:
# v1: 2020-01-01 - 张三:初始实现
# v2: 2021-05-15 - 李四:修复了一个bug
# v3: 2022-08-20 - 王五:添加了新功能
def some_function():
...
这些信息应该在 git 历史里,而不是代码里。
好:
def some_function():
# 这个函数处理用户登录
# 注意:支持 OAuth 和传统密码两种方式
# 历史背景:2022-08 添加 OAuth 支持(issue #456)
...
只保留对理解代码有帮助的历史信息。
检查清单
写完注释后,检查:
总结
注释的哲学:
- 注释不是说明,而是承认 —— 承认代码无法完全自解释
- 好的注释解释"为什么" —— 而不是"做什么"
- 删掉重复代码的注释 —— 它们是噪音
- 不要用注释为糟糕的代码道歉 —— 重构代码
- 注释需要维护 —— 过时的注释比没有注释更糟糕
- AI 生成的注释需要人工改进 —— 添加业务背景和"为什么"
记住:
最好的注释是不需要的注释——因为代码已经清楚表达了意图。
次好的注释是解释代码做不到的——业务背景、历史原因、权衡决策。
最差的注释是重复代码或为代码道歉的——它们应该被删除或重构。

浙公网安备 33010602011771号