代码重构的艺术:如何写出优雅可维护的代码


代码重构的艺术:如何写出优雅可维护的代码

代码重构是在不改变代码外部行为的前提下,改善代码内部结构的过程。重构不是重写,而是一系列小而安全的改动,逐步提升代码质量。Martin Fowler说过:"任何傻瓜都能写出计算机能理解的代码,优秀的程序员写出人能理解的代码。"本文将介绍代码重构的核心原则和常用手法,帮助你在日常开发中持续改善代码质量。

一、何时需要重构

识别代码坏味道(Code Smell)是重构的第一步:

1. 过长的函数

// 坏味道:一个函数做了太多事情
function processOrder($orderData) {
    // 验证数据
    if (empty($orderData["items"])) return false;
    foreach ($orderData["items"] as $item) {
        if ($item["quantity"] <= 0) return false;
    }
    
    // 计算价格
    $total = 0;
    foreach ($orderData["items"] as $item) {
        $total += $item["price"] * $item["quantity"];
    }
    $tax = $total * 0.1;
    $finalAmount = $total + $tax;
    
    // 扣减库存
    foreach ($orderData["items"] as $item) {
        $stock = Stock::find($item["product_id"]);
        $stock->quantity -= $item["quantity"];
        $stock->save();
    }
    
    // 创建订单
    $order = Order::create([
        "user_id" => $orderData["user_id"],
        "total" => $finalAmount,
        "status" => "pending"
    ]);
    
    // 发送邮件通知
    mail($user->email, "订单确认", "您的订单已创建");
    
    return $order;
}

// 重构后:每个函数只做一件事
function processOrder(array $orderData): Order
{
    $this->validateOrderData($orderData);
    $amount = $this->calculateOrderAmount($orderData["items"]);
    $this->deductStock($orderData["items"]);
    $order = $this->createOrder($orderData, $amount);
    $this->sendOrderNotification($order);
    return $order;
}

private function validateOrderData(array $data): void
{
    if (empty($data["items"])) {
        throw new InvalidArgumentException("订单商品不能为空");
    }
    foreach ($data["items"] as $item) {
        if ($item["quantity"] <= 0) {
            throw new InvalidArgumentException("商品数量必须大于0");
        }
    }
}

private function calculateOrderAmount(array $items): float
{
    $subtotal = array_reduce($items, fn($sum, $item) => 
        $sum + $item["price"] * $item["quantity"], 0);
    return $subtotal * 1.1;  // 含税
}

2. 重复代码(DRY原则)

// 坏味道:重复的查询逻辑
function getUserOrders($userId) {
    return DB::table("orders")
        ->where("user_id", $userId)
        ->where("status", "!=", "cancelled")
        ->orderBy("created_at", "desc")
        ->get();
}

function getUserOrderCount($userId) {
    return DB::table("orders")
        ->where("user_id", $userId)
        ->where("status", "!=", "cancelled")
        ->count();
}

// 重构:提取公共查询条件
function activeOrdersQuery($userId) {
    return DB::table("orders")
        ->where("user_id", $userId)
        ->where("status", "!=", "cancelled");
}

function getUserOrders($userId) {
    return $this->activeOrdersQuery($userId)
        ->orderBy("created_at", "desc")->get();
}

function getUserOrderCount($userId) {
    return $this->activeOrdersQuery($userId)->count();
}

3. 魔法数字和硬编码

// 坏味道
if ($user["role"] == 1) { /* 管理员 */ }
if ($order["status"] == 3) { /* 已完成 */ }

// 重构:使用常量或枚举
class UserRole {
    const ADMIN = 1;
    const EDITOR = 2;
    const USER = 3;
}

enum OrderStatus: int {
    case Pending = 1;
    case Processing = 2;
    case Completed = 3;
    case Cancelled = 4;
}

if ($user->role === UserRole::ADMIN) { /* ... */ }
if ($order->status === OrderStatus::Completed) { /* ... */ }

二、重构手法

1. 提取方法

// 当一段代码需要注释才能理解时,把它提取成一个方法
// 方法名就是最好的注释

// Before
function renderPage($data) {
    // 过滤敏感词
    $filtered = [];
    foreach ($data["content"] as $word) {
        if (!in_array($word, $sensitiveWords)) {
            $filtered[] = $word;
        }
    }
    // ...
}

// After
function renderPage($data) {
    $safeContent = $this->filterSensitiveWords($data["content"]);
    // ...
}

2. 以多态替代条件

// 坏味道:大量if-else或switch
function calculateShipping($type, $weight) {
    switch ($type) {
        case "standard": return $weight * 5;
        case "express": return $weight * 10 + 20;
        case "overnight": return $weight * 20 + 50;
        case "international": return $weight * 30 + 100;
    }
}

// 重构:使用策略模式
interface ShippingCalculator {
    public function calculate(float $weight): float;
}

class StandardShipping implements ShippingCalculator {
    public function calculate(float $weight): float { return $weight * 5; }
}

class ExpressShipping implements ShippingCalculator {
    public function calculate(float $weight): float { return $weight * 10 + 20; }
}

// 新增配送方式不需要修改已有代码

三、重构的安全实践

  • 小步前进:每次只做一个小改动,确保每步都能通过测试
  • 先写测试:有测试保护才能放心重构
  • 频繁提交:每完成一个安全的重构步骤就提交
  • 使用IDE重构功能:重命名、提取方法等操作让IDE来完成
  • 不要混合重构和功能开发:重构的提交不应包含功能变更

代码重构不是一次性工程,而是日常开发的一部分。遵循"童子军规则":每次修改代码时,都让它比之前稍微好一点。长期坚持下来,代码质量会显著提升,维护成本也会大幅降低。


0.078208s