1.1.1 抽象的代价:为什么抽象是一种未来赌注
1.1.1 抽象的代价:为什么抽象是一种未来赌注
引言
很多程序员在写代码时都被教导要"抽象"、"解耦"、"面向接口编程"。这些原则没有错,但它们常常被过度使用。抽象不是免费的——每一次抽象都是在为未来的变化下注,而这个赌注可能赢,也可能输。

抽象的本质是什么
抽象是一种提前猜测的行为。当你把一段具体的逻辑抽象成一个通用函数或类时,你实际上在说:"我猜测未来会有类似的需求,所以我现在就把它抽象出来。"
但问题在于:你真的知道未来会怎样吗?
一个常见的场景
假设你正在写一个电商系统的订单处理模块:
# 版本1:具体实现
def create_order(user_id, product_id, quantity):
product = db.get_product(product_id)
price = product.price * quantity
order = Order(user_id=user_id, product_id=product_id,
quantity=quantity, total_price=price)
db.save(order)
send_email(user_id, "订单创建成功")
return order
一个有一年经验的程序员可能会觉得这段代码"不够优雅",于是开始抽象:
# 版本2:过度抽象
class OrderCreationStrategy(ABC):
@abstractmethod
def calculate_price(self, product, quantity): pass
@abstractmethod
def notify_user(self, user_id, message): pass
class StandardOrderCreationStrategy(OrderCreationStrategy):
def calculate_price(self, product, quantity):
return product.price * quantity
def notify_user(self, user_id, message):
send_email(user_id, message)
class OrderService:
def __init__(self, strategy: OrderCreationStrategy):
self.strategy = strategy
def create_order(self, user_id, product_id, quantity):
product = db.get_product(product_id)
price = self.strategy.calculate_price(product, quantity)
order = Order(user_id=user_id, product_id=product_id,
quantity=quantity, total_price=price)
db.save(order)
self.strategy.notify_user(user_id, "订单创建成功")
return order
这段代码看起来"更专业",但它带来了什么?
抽象的隐性成本
1. 认知负担增加
原本5行代码就能看懂的逻辑,现在需要跳转到3个不同的文件才能理解。新人接手时,他需要回答:
OrderCreationStrategy是什么?- 为什么要有这个抽象层?
- 目前有几种
Strategy实现? - 我要修改逻辑时,应该改哪一层?
2. 变更成本上升
如果需求变更,比如订单创建时需要增加库存检查:
版本1(具体实现):直接在函数里加一行代码,30秒搞定。
版本2(抽象版):你需要思考:
- 这个逻辑应该加在
OrderService里还是Strategy里? - 如果加在
Strategy,是不是所有实现都要加? - 会不会破坏现有的抽象设计?
结果:本来30秒的事情,变成了30分钟的思考 + 可能的重构。
3. 未来变化的方向可能完全错误
最致命的问题是:你抽象的方向可能根本不是未来需要的方向。
上面的例子中,你假设未来会有不同的"价格计算策略"和"通知策略"。但实际情况可能是:
- 未来需要的是不同的"支付方式"(微信、支付宝、信用卡)
- 或者需要的是"订单状态流转"的抽象
- 而你抽象的"价格计算"根本没有变化过
结果:你的抽象成了无用的复杂度,既没有带来灵活性,又增加了维护成本。
何时应该抽象
抽象不是不好,而是需要基于真实发生的变化,而不是想象中的变化。
Rule of Three(三次原则)
当一段逻辑第三次出现时,再考虑抽象。
- 第1次:写具体代码
- 第2次:复制粘贴,容忍重复
- 第3次:现在你知道它们的共同点和差异点了,抽象
基于痛点抽象,而非基于美学
不要因为"代码看起来不够优雅"就抽象,而是因为"重复代码导致了实际的维护痛点"才抽象。
反例(基于美学):
# "我觉得这两个函数很相似,应该合并"
def send_welcome_email(user):
send_email(user.email, "欢迎")
def send_reset_password_email(user):
send_email(user.email, "重置密码")
# 抽象后
def send_notification(user, type):
messages = {"welcome": "欢迎", "reset": "重置密码"}
send_email(user.email, messages[type])
这个抽象有什么问题?当未来"欢迎邮件"需要加用户名,而"重置密码邮件"需要加过期时间,这个抽象就会崩溃。
正例(基于痛点):
# 当你发现自己在10个地方都需要写这段代码时
if user.is_premium:
discount = 0.8
else:
discount = 1.0
price = base_price * discount
# 此时抽象是合理的
def calculate_user_price(user, base_price):
discount = 0.8 if user.is_premium else 1.0
return base_price * discount
对使用AI编程的程序员的特别提醒
如果你正在使用 AI(如 ChatGPT、Claude、Copilot)辅助编程,你需要特别警惕:
AI 倾向于生成"看起来专业"的代码,而不是"刚好够用"的代码。
AI 会本能地使用设计模式、抽象层、接口等,因为这些在训练数据中被标记为"好代码"。但它不知道你的项目规模、团队水平、未来规划。
实用建议
- 当 AI 生成抽象代码时,问自己:"如果去掉这个抽象层,代码会变得难以维护吗?"
- 主动要求 AI 简化:"请给我一个最简单的实现,不要使用设计模式。"
- 理解每一行代码的存在理由:如果你不能用一句话解释某个抽象的价值,就删掉它。
实践原则
YAGNI (You Aren't Gonna Need It)
你不会需要它的。
在确凿的需求出现之前,不要为"可能的未来"编写代码。
渐进式抽象
抽象应该是一个渐进的过程,而不是一次性的设计:
具体代码 → 发现重复 → 提取函数 → 发现模式 → 抽象接口
而不是:
需求 → 直接设计抽象层
可逆性思维
好的抽象应该是容易拆掉的。如果你发现一个抽象不再有用,能否在10分钟内把它还原成具体代码?
如果不能,说明这个抽象耦合得太深了。
总结
抽象是工程师的重要工具,但不是唯一工具。记住:
- 抽象是一种赌注——你在赌未来会有某种变化
- 抽象有成本——认知负担、变更成本、错误方向的风险
- 基于事实抽象——用"三次原则",用真实的重复痛点驱动抽象
- 对 AI 生成的抽象保持警惕——简单优于复杂
- YAGNI——直到你真正需要时再抽象
好的程序员不是擅长抽象的人,而是知道何时不抽象的人。

浙公网安备 33010602011771号