1-3-3-函数哲学
1.3.3 函数哲学:遵循单一职责的「道」
引言
函数是代码组织的基本单元。
一个好的函数就像一个好的工具:
- 它只做一件事
- 它把这件事做好
- 它的名字清楚表达了它做什么
但现实中,函数常常变成"瑞士军刀"——什么都能做,但每件事都做得不够好。
函数的单一职责原则(SRP)不仅仅是技术原则,而是一种思维方式——「道」。
什么是"一件事"
错误理解:一件事 = 一行代码
# 这不是单一职责,这是过度简化
def a(x): return x * 2
def b(x): return x + 1
def c(x): return b(a(x))
正确理解:一件事 = 一个抽象层次
def process_user_registration(registration_data):
"""处理用户注册(一个完整的业务流程)"""
# 这个函数做"一件事":用户注册
# 但这件事包含多个步骤
validate_registration_data(registration_data)
user = create_user_account(registration_data)
send_welcome_email(user)
log_registration_event(user)
return user
这个函数做"一件事":处理用户注册。
虽然包含4个步骤,但它们都在同一个抽象层次上。
单一职责的判断标准
标准1:函数只有一个修改的理由
# 违反SRP:有两个修改理由
def save_user_and_send_email(user):
# 理由1:用户存储逻辑变化
db.save(user)
# 理由2:邮件发送逻辑变化
email_service.send(user.email, "Welcome")
# 遵循SRP:拆分成两个函数
def save_user(user):
"""保存用户(只有存储逻辑变化时才修改)"""
db.save(user)
def send_welcome_email(user):
"""发送欢迎邮件(只有邮件逻辑变化时才修改)"""
email_service.send(user.email, "Welcome")
标准2:函数的所有语句在同一抽象层次
# 不好:混合了不同抽象层次
def process_order(order):
# 高层抽象
if order.is_valid():
# 突然跳到底层细节
db.execute("INSERT INTO orders VALUES (...)")
# 又回到高层
send_confirmation(order)
# 好:保持同一抽象层次
def process_order(order):
# 所有语句都是高层操作
validate_order(order)
save_order(order)
send_confirmation(order)
def save_order(order):
# 底层细节封装在这里
db.execute("INSERT INTO orders VALUES (...)")
标准3:函数可以用一句话描述(不用"和")
# 不好:需要用"和"
def validate_and_save_and_notify(user):
"""验证用户 **和** 保存用户 **和** 发送通知"""
...
# 好:每个函数一句话
def register_user(user):
"""注册用户(这是一个完整的业务流程)"""
validate_user(user)
save_user(user)
notify_user(user)
函数大小的哲学
多大合适?
经验法则:
- 理想:一个屏幕(20-30行)
- 警告线:50行
- 危险线:100行
但行数不是绝对标准,更重要的是:
- 你能在30秒内理解它做什么吗?
- 它只做一件事吗?
- 它的抽象层次一致吗?
案例:一个太长的函数
def process_order(order_data):
# 验证(20行)
if 'user_id' not in order_data:
raise ValueError("Missing user_id")
if 'items' not in order_data:
raise ValueError("Missing items")
# ... 更多验证
# 计算价格(30行)
subtotal = 0
for item in order_data['items']:
if item['quantity'] < 0:
raise ValueError("Invalid quantity")
subtotal += item['price'] * item['quantity']
tax = subtotal * 0.1
if order_data.get('coupon'):
discount = calculate_coupon_discount(order_data['coupon'])
subtotal -= discount
total = subtotal + tax
# ... 更多计算
# 保存(15行)
order = Order(
user_id=order_data['user_id'],
items=order_data['items'],
total=total
)
db.save(order)
# 通知(20行)
user = db.get_user(order_data['user_id'])
send_email(user.email, "Order confirmation", ...)
send_sms(user.phone, "Order placed")
# ...
return order
问题:
- 85行,太长
- 混合了4个不同的职责
- 难以测试(必须测试整个流程)
- 难以复用(不能单独使用"验证"或"计算价格")
重构:拆分职责
def process_order(order_data):
"""处理订单(高层协调)"""
validate_order_data(order_data)
total = calculate_order_total(order_data)
order = save_order(order_data, total)
notify_user_about_order(order)
return order
def validate_order_data(order_data):
"""验证订单数据"""
required_fields = ['user_id', 'items']
for field in required_fields:
if field not in order_data:
raise ValueError(f"Missing {field}")
for item in order_data['items']:
if item['quantity'] < 0:
raise ValueError("Invalid quantity")
def calculate_order_total(order_data):
"""计算订单总价"""
subtotal = sum(
item['price'] * item['quantity']
for item in order_data['items']
)
subtotal = apply_coupon_discount(subtotal, order_data.get('coupon'))
tax = subtotal * 0.1
return subtotal + tax
def save_order(order_data, total):
"""保存订单到数据库"""
order = Order(
user_id=order_data['user_id'],
items=order_data['items'],
total=total
)
db.save(order)
return order
def notify_user_about_order(order):
"""通知用户订单已创建"""
user = db.get_user(order.user_id)
send_email(user.email, "Order confirmation", ...)
send_sms(user.phone, "Order placed")
好处:
- 每个函数< 15行
- 每个函数只做一件事
- 容易测试(可以单独测试每个步骤)
- 容易复用(可以在其他地方使用
calculate_order_total) - 容易理解(每个函数一眼就能看懂)
参数的哲学
参数数量
理想:0-2 个参数
可接受:3 个参数
警告:4 个参数
重构:> 4 个参数
# 不好:太多参数
def create_user(name, email, phone, address, city, state, zip_code, country):
...
# 好:用对象封装
def create_user(user_data):
"""
Args:
user_data: UserRegistrationData 对象
"""
...
# 或者用数据类
from dataclasses import dataclass
@dataclass
class UserRegistrationData:
name: str
email: str
phone: str
address: str
city: str
state: str
zip_code: str
country: str
def create_user(data: UserRegistrationData):
...
参数顺序
# 不好:顺序不直观
def copy_file(destination, source): # 顺序反了
...
# 好:符合直觉的顺序
def copy_file(source, destination): # 从哪里到哪里
...
# 好:必填参数在前,可选参数在后
def search_users(keyword, limit=10, offset=0):
...
布尔参数:代码异味
# 不好:布尔参数让调用点不清楚
def save_user(user, send_email):
db.save(user)
if send_email:
send_welcome_email(user)
# 调用点
save_user(user, True) # True 是什么意思?
# 好:拆分成两个函数
def save_user(user):
db.save(user)
def save_user_and_send_welcome_email(user):
save_user(user)
send_welcome_email(user)
# 或者:用枚举让意图清晰
from enum import Enum
class NotificationPreference(Enum):
SEND_EMAIL = 1
NO_EMAIL = 2
def save_user(user, notification=NotificationPreference.SEND_EMAIL):
db.save(user)
if notification == NotificationPreference.SEND_EMAIL:
send_welcome_email(user)
# 调用点
save_user(user, NotificationPreference.NO_EMAIL) # 清楚多了
函数的副作用
显式 vs 隐式副作用
# 危险:隐式副作用
def get_user(user_id):
"""看起来是查询,实际上会修改状态"""
user = db.query(User).get(user_id)
user.last_accessed = datetime.now() # 副作用!
db.save(user)
return user
# 好:名字明确表明会修改状态
def get_user_and_update_last_accessed(user_id):
user = db.query(User).get(user_id)
user.last_accessed = datetime.now()
db.save(user)
return user
# 更好:分离查询和修改
def get_user(user_id):
"""纯查询,无副作用"""
return db.query(User).get(user_id)
def update_user_last_accessed(user):
"""明确的修改操作"""
user.last_accessed = datetime.now()
db.save(user)
命令查询分离(CQS)
原则:一个函数要么是"命令"(修改状态),要么是"查询"(返回值),不应该两者都是。
# 违反CQS
def pop_and_return(stack):
"""既修改了 stack,又返回了值"""
if stack:
return stack.pop()
return None
# 遵循CQS(不过这个例子中,pop 的语义就是"移除并返回",是可接受的例外)
# 更好的例子:
# 违反CQS
def get_and_increment_counter():
global counter
counter += 1 # 修改状态
return counter # 返回值
# 遵循CQS
def get_counter():
"""查询:只返回值"""
return counter
def increment_counter():
"""命令:只修改状态"""
global counter
counter += 1
错误处理的哲学
不要返回 None 表示错误
# 不好:用 None 表示错误,和"找不到"混淆
def divide(a, b):
if b == 0:
return None # 错误?还是找不到?
return a / b
# 使用时必须记得检查
result = divide(10, 0)
if result is not None:
print(result)
# 好:用异常表示错误
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
# 或者:用结果对象
from dataclasses import dataclass
from typing import Optional
@dataclass
class DivisionResult:
success: bool
value: Optional[float] = None
error: Optional[str] = None
def divide(a, b) -> DivisionResult:
if b == 0:
return DivisionResult(success=False, error="Division by zero")
return DivisionResult(success=True, value=a / b)
错误处理不应该模糊主逻辑
# 不好:错误处理淹没了主逻辑
def process_payment(order):
try:
user = get_user(order.user_id)
except UserNotFound:
log_error("User not found")
return False
try:
card = get_payment_method(user)
except NoPaymentMethod:
log_error("No payment method")
return False
try:
charge_card(card, order.total)
except PaymentFailed as e:
log_error(f"Payment failed: {e}")
return False
return True
# 好:主逻辑清晰
def process_payment(order):
"""处理支付(抛出异常表示失败)"""
user = get_user(order.user_id) # 可能抛出 UserNotFound
card = get_payment_method(user) # 可能抛出 NoPaymentMethod
charge_card(card, order.total) # 可能抛出 PaymentFailed
return True
# 调用方处理错误
try:
process_payment(order)
except (UserNotFound, NoPaymentMethod, PaymentFailed) as e:
log_error(f"Payment processing failed: {e}")
notify_user(order.user_id, "Payment failed")
对使用 AI 的程序员的建议
AI 生成的函数常常违反单一职责原则。
AI 的常见问题
# AI 可能生成这样的函数
def handle_user_registration(data):
# 做太多事情
if not validate_email(data['email']):
return {"error": "Invalid email"}
if User.exists(email=data['email']):
return {"error": "Email already exists"}
user = User(**data)
db.save(user)
token = generate_verification_token(user)
send_verification_email(user.email, token)
log_event("user_registered", user.id)
return {"success": True, "user_id": user.id}
如何改进
请把这个函数拆分成多个小函数,每个函数只做一件事:
1. validate_registration_data - 验证数据
2. create_user_account - 创建用户
3. send_verification_email - 发送验证邮件
4. log_registration - 记录日志
主函数 register_user 协调这些步骤。
函数组织的最佳实践
1. 降序阅读:高层函数在前,低层函数在后
# 好:从高到低
def process_order(order_data):
"""高层函数"""
validate(order_data)
save(order_data)
notify(order_data)
def validate(order_data):
"""中层函数"""
check_required_fields(order_data)
check_inventory(order_data)
def check_required_fields(order_data):
"""底层函数"""
# 具体实现
...
2. 文件组织:按功能分组
# user_service.py
# Public API(外部调用的函数)
def register_user(data):
...
def login_user(username, password):
...
# Private helpers(内部辅助函数)
def _validate_registration_data(data):
...
def _send_welcome_email(user):
...
检查清单
写完函数后,检查:
总结
函数的单一职责之道:
- 一个函数只做一件事——但"一件事"是在同一抽象层次上
- 函数应该小——理想是一个屏幕内能看完
- 参数应该少——超过3个就考虑用对象封装
- 副作用要显式——在函数名中体现
- 错误处理要清晰——不要淹没主逻辑
- 降序阅读——高层函数在前,底层函数在后
记住:
写函数就像写文章的段落——一个段落只表达一个观点。
好的函数让代码读起来像一系列清晰的步骤,而不是一团混乱的逻辑。
当你发现一个函数难以命名时,可能是它做了太多事情——拆分它。

浙公网安备 33010602011771号