1-2-3-代码的记忆性
1.2.3 代码的记忆性:系统不会忘记你的任何一次妥协
引言
大脑会忘记,文档会过时,团队会换人。
但代码有完美的记忆。
你三年前为了赶项目上线做的临时妥协,今天仍然在系统里运行。你当时想的"以后再改",可能永远不会被改。
系统不会忘记你的任何一次妥协,它会记住,并让你在未来付出代价。
代码的记忆特性
1. 代码会记住你的每一个决策
# 2020年,你写下这段代码
def calculate_shipping_cost(order):
# TODO: 临时硬编码,等API ready后改
if order.country == 'US':
return 10
else:
return 20
2023年,这段代码还在运行。
为什么?
- 功能"能用",没人有动力改
- 新人不知道这是"临时方案"
- 负责的人早已离职
- 真正的 API 可能开发了,但没人知道应该替换这里
系统记住了你当初的妥协,并把它当作"正式方案"运行了三年。
2. 代码会记住你的每一个假设
// 你在2019年写的代码
function getUserName(user) {
// 假设所有用户都有 firstName 和 lastName
return `${user.firstName} ${user.lastName}`;
}
2021年,产品需求变了:
- 支持单一名字的用户(如印尼用户)
- 支持企业用户(没有姓名,只有公司名)
- 支持匿名用户
但代码还记得 2019 年的假设:所有用户都有 firstName 和 lastName。
结果:
- 调用这个函数的地方有 200 处
- 每次调用前都需要判断
user.firstName是否存在 - 或者,函数返回
undefined undefined
系统记住了你当初的假设,并用这个假设限制了未来的扩展。
3. 代码会记住你的每一个Bug修复
# 2020年:原始代码
def send_email(user, subject, body):
email_service.send(user.email, subject, body)
# 2021年:修复了一个 bug
def send_email(user, subject, body):
# Fix: 某些用户的邮箱字段可能为空
if user.email:
email_service.send(user.email, subject, body)
# 2022年:又修复了一个 bug
def send_email(user, subject, body):
if user.email:
# Fix: 某些用户的邮箱格式不正确
if '@' in user.email:
email_service.send(user.email, subject, body)
# 2023年:再修一个 bug
def send_email(user, subject, body):
if user.email:
if '@' in user.email:
# Fix: 某些用户禁用了邮件通知
if user.email_notifications_enabled:
email_service.send(user.email, subject, body)
系统记住了每一次修复,函数越来越复杂。
但没人知道:
- 哪些判断是必要的
- 为什么要有这些判断
- 能否简化
结果:函数成了"屎山",没人敢动。
临时方案的永久性
"临时方案"是最持久的方案
有一个软件工程的笑话:
没有什么比临时方案更持久的了。
为什么?
1. "能用"的惯性
# 你写的临时方案
def get_user_permissions(user_id):
# TEMP: 先硬编码,等权限系统上线后改
if user_id in [1, 2, 3]: # 管理员ID
return ['admin']
else:
return ['user']
三年后,权限系统早已上线,但这段代码还在跑。
因为:
- 它"能用"
- 改它需要测试
- 测试需要时间
- 项目经理说"先做新功能,这个不紧急"
"临时"变成了"永久"。
2. 知识的流失
6 个月后:
- 你忘了这是临时方案
- 新人以为这是正式方案
- 代码审查时没人提出质疑
1 年后:
- 你离职了
- 新人基于这个"临时方案"写了更多代码
3 年后:
- 没人知道这是临时的
- 改它的成本已经高到没人愿意改
案例:Win32 API 的向后兼容
Windows API 中有无数历史遗留的"临时方案"。
GetWindowTextLengthA 函数:
- 在 Windows 95 时代添加
- 原本是"临时"支持 ANSI 字符串
- Unicode 版本(GetWindowTextLengthW)才是"正式"版本
但 2024 年,30年后,GetWindowTextLengthA 仍然存在。
为什么不删除?
- 有成千上万的程序依赖它
- 删除它会破坏向后兼容性
- 微软不得不永久维护这个"临时"API
教训:一旦代码被发布,它就可能永远无法被删除。
妥协的累积效应
小妥协 → 大问题
单个妥协可能无害,但妥协会累积:
# 妥协1:硬编码配置(2020年)
MAX_UPLOAD_SIZE = 10 * 1024 * 1024 # 10MB
# 妥协2:特殊处理某个客户(2021年)
def get_max_upload_size(user):
if user.email.endswith('@bigclient.com'):
return 50 * 1024 * 1024 # 特殊客户允许 50MB
return MAX_UPLOAD_SIZE
# 妥协3:再特殊处理一个客户(2022年)
def get_max_upload_size(user):
if user.email.endswith('@bigclient.com'):
return 50 * 1024 * 1024
if user.email.endswith('@enterprise.com'):
return 100 * 1024 * 1024
return MAX_UPLOAD_SIZE
# 妥协4:VIP用户也要特殊处理(2023年)
def get_max_upload_size(user):
if user.email.endswith('@bigclient.com'):
return 50 * 1024 * 1024
if user.email.endswith('@enterprise.com'):
return 100 * 1024 * 1024
if user.is_vip:
return 200 * 1024 * 1024
return MAX_UPLOAD_SIZE
4 年后,这个函数已经无法维护了。
正确的做法应该是:在第一次妥协时就设计一个合理的权限系统。
但没人在第一次妥协时这么想,大家都觉得"就这一次"。
技术债的复利效应
技术债和金融债务类似,会产生"利息"。
第1年:
- 你偷了一个懒,硬编码了一个值
- 成本:0
第2年:
- 需求变更,你又加了一个硬编码
- 成本:5 分钟
第3年:
- 需求又变,你需要找到所有硬编码的地方
- 成本:1 小时
第4年:
- 硬编码已经分散在 20 个文件中
- 需要改成可配置的
- 成本:3 天重构 + 2 天测试
如果第1年就做对,成本:30 分钟。
拖了4年,成本:5 天。
这就是技术债的复利。
如何对抗代码的记忆性
1. 不要写"临时"代码
如果你发现自己在想:
"先这样临时处理,以后再改。"
停下来,问自己:
- "以后"是什么时候? 如果没有明确的时间表,就没有"以后"。
- 谁会来改? 如果不是你自己,可能永远不会被改。
- 改它的动力是什么? 如果"能用",就没人有动力改。
更好的选择:
要么现在就做对,要么接受它会永久存在的事实。
如果确实没时间做对:
def get_user_permissions(user_id):
# WARNING: 这是临时方案,硬编码了管理员ID
# TODO: 在 2024-Q1 迁移到权限系统(负责人:Alice)
# Tracking issue: #1234
if user_id in [1, 2, 3]:
return ['admin']
else:
return ['user']
至少:
- 明确这是临时的
- 有具体的计划
- 有负责人
- 有 issue 追踪
2. 记录你的假设
如果代码基于某个假设:
def calculate_price(items):
# 假设:所有商品价格 > 0
# 假设:所有商品都有 price 字段
# 假设:不会有折扣或优惠券(暂不支持)
return sum(item['price'] * item['quantity'] for item in items)
当假设不再成立时,这段代码需要重写。
更好的做法:用断言(assertion)强制假设:
def calculate_price(items):
total = 0
for item in items:
assert 'price' in item, "Item must have a price"
assert item['price'] > 0, "Price must be positive"
total += item['price'] * item['quantity']
return total
当假设被违反时,程序会立即崩溃,强制你修复。
而不是默默地产生错误结果,让 bug 潜伏几个月。
3. 定期清理技术债
建立技术债清单:
# Technical Debt
## High Priority
- [ ] 将硬编码的权限逻辑迁移到权限系统 (#1234)
- [ ] 移除临时的邮件发送逻辑 (#1235)
## Medium Priority
- [ ] 重构 `calculate_price` 函数,支持优惠券 (#1236)
## Low Priority
- [ ] 统一错误处理方式 (#1237)
每个 Sprint 分配时间还债:
- 不要把 100% 的时间都用在新功能上
- 分配 10-20% 的时间还技术债
- 否则债务会越积越多
4. 用类型系统强制正确性
弱类型的风险:
def send_email(user, subject, body):
# 假设 user.email 存在且格式正确
email_service.send(user.email, subject, body)
强类型的保护:
from typing import Optional
class User:
email: Optional[str] # 明确表示 email 可能为空
def send_email(user: User, subject: str, body: str) -> bool:
if user.email is None:
return False
if not is_valid_email(user.email):
return False
email_service.send(user.email, subject, body)
return True
类型系统会强制你处理 email 可能为空的情况。
5. 写测试固化预期
测试是另一种"记忆"——它记住了代码应该做什么。
def test_calculate_price():
items = [
{'price': 10, 'quantity': 2},
{'price': 5, 'quantity': 3}
]
assert calculate_price(items) == 35
def test_calculate_price_with_zero_quantity():
items = [{'price': 10, 'quantity': 0}]
assert calculate_price(items) == 0
def test_calculate_price_with_negative_price_raises_error():
items = [{'price': -10, 'quantity': 1}]
with pytest.raises(AssertionError):
calculate_price(items)
当你修改代码时,测试会告诉你是否破坏了原有的预期。
对使用 AI 的程序员的建议
AI 不会记得项目的历史:
- 哪些是临时方案
- 哪些假设已经过时
- 哪些代码应该被重构
AI 倾向于延续现有模式
如果你的代码库中有临时方案:
# 你的旧代码
if user_id in [1, 2, 3]:
return ['admin']
AI 可能会模仿这种模式:
# AI 生成的代码
if product_id in [100, 101, 102]: # 特殊产品
return apply_discount(price, 0.5)
结果:AI 帮你传播了糟糕的模式。
如何避免
-
清理代码库后再让 AI 学习
- 不要让 AI 基于充满技术债的代码生成新代码
- 先重构关键模块,建立好的模式
-
明确告诉 AI 避免什么
请帮我实现用户权限检查,要求: - 不要硬编码用户ID - 使用数据库中的权限表 - 参考 auth/permissions.py 中的模式 -
审查 AI 生成的代码
- 检查是否有硬编码
- 检查是否有隐式假设
- 检查是否会产生技术债
真实案例
Y2K 问题(千年虫)
1960-1990年代,为了节省存储空间,程序员用两位数字表示年份:
YEAR = 99 * 表示 1999年
这是当时的"临时方案":
- 存储空间很贵
- "反正 2000 年之前肯定会升级系统"
结果:
- 2000 年临近时,全世界的系统都面临崩溃风险
- 花费数千亿美元修复
- 有些系统到今天还有这个问题
教训:
- "临时方案"会活得比你想象的长
- 假设未来会解决的问题,往往不会被解决
PHP 的历史包袱
PHP 语言有很多历史遗留的糟糕设计:
// 函数命名不一致
strpos() // 查找字符串
str_replace() // 为什么有下划线?
// 参数顺序不一致
array_filter($array, $callback)
array_map($callback, $array) // 顺序反了
为什么不修复?
- 向后兼容性
- 数百万个网站依赖这些 API
- 修复会破坏现有代码
教训:一旦 API 被发布,它就很难改变。第一次就要设计好。
检查清单
完成代码后,问自己:
如果有任何一项答案是"会"或"不确定",考虑改进。
总结
代码的记忆性:
- 系统会记住你的每一个决策——包括糟糕的决策
- 临时方案往往是最持久的——一旦"能用",就很难有动力改
- 技术债会复利增长——拖得越久,成本越高
- 假设会限制未来的扩展——要么用类型/断言强制,要么明确记录
- 代码是你留给未来的遗产——可能是财富,也可能是负担
记住:
你今天的妥协,是明天的技术债。
你今天的假设,是未来的限制。
你今天的"临时方案",可能会运行10年。
所以,第一次就尽量做对。如果做不对,至少留下清晰的记录,告诉未来的维护者(可能就是你自己):这里有坑。

浙公网安备 33010602011771号