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)
    ...

只保留对理解代码有帮助的历史信息。

检查清单

写完注释后,检查:

总结

注释的哲学:

  1. 注释不是说明,而是承认 —— 承认代码无法完全自解释
  2. 好的注释解释"为什么" —— 而不是"做什么"
  3. 删掉重复代码的注释 —— 它们是噪音
  4. 不要用注释为糟糕的代码道歉 —— 重构代码
  5. 注释需要维护 —— 过时的注释比没有注释更糟糕
  6. AI 生成的注释需要人工改进 —— 添加业务背景和"为什么"

记住:

最好的注释是不需要的注释——因为代码已经清楚表达了意图。

次好的注释是解释代码做不到的——业务背景、历史原因、权衡决策。

最差的注释是重复代码或为代码道歉的——它们应该被删除或重构。

posted @ 2025-11-29 21:54  Jack_Q  阅读(0)  评论(0)    收藏  举报