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

检查清单

写完函数后,检查:

总结

函数的单一职责之道:

  1. 一个函数只做一件事——但"一件事"是在同一抽象层次上
  2. 函数应该小——理想是一个屏幕内能看完
  3. 参数应该少——超过3个就考虑用对象封装
  4. 副作用要显式——在函数名中体现
  5. 错误处理要清晰——不要淹没主逻辑
  6. 降序阅读——高层函数在前,底层函数在后

记住:

写函数就像写文章的段落——一个段落只表达一个观点。

好的函数让代码读起来像一系列清晰的步骤,而不是一团混乱的逻辑。

当你发现一个函数难以命名时,可能是它做了太多事情——拆分它。

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