代码重构的艺术:如何写出优雅可维护的代码
代码重构是在不改变代码外部行为的前提下,改善代码内部结构的过程。重构不是重写,而是一系列小而安全的改动,逐步提升代码质量。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来完成
- 不要混合重构和功能开发:重构的提交不应包含功能变更
代码重构不是一次性工程,而是日常开发的一部分。遵循"童子军规则":每次修改代码时,都让它比之前稍微好一点。长期坚持下来,代码质量会显著提升,维护成本也会大幅降低。