技术分享

拯救400行“屎山”代码!我重构了Laravel仓库模式,性能和维护性双提升

作者头像 人称外号大脸猫
17 阅读
拯救400行“屎山”代码!我重构了Laravel仓库模式,性能和维护性双提升

本文记录了我如何将一个400行的Repository方法拆解成可维护组件的真实历程。如果你的代码也变成了“屎山”,或许我的经验能帮到你。

我们团队的项目中有一个OrderRepository,其中getAdminIndex方法已经膨胀到400多行。它负责管理后台的订单列表展示,包含查询构建、数据统计、分页处理、结果加工等十多种职责。

看着这段代码,我仿佛听到了它的哭泣:

public function getAdminIndex($params, $isTj = false)
{
    // 光是查询条件就有30多个if判断
    // 数据处理又来了几十行计算逻辑
    // 这还只是方法的一半内容...
}

为什么要重构?

这个问题让我夜不能寐:明明我们已经按照Repository模式架构了,为什么还会出现这种庞然大物?

答案很简单:我们只是机械地分层,却没有真正践行单一职责原则。这个方法的“罪行”包括:

  1. 查询构建与业务逻辑耦合:30多个查询条件堆在一起
  2. 数据转换与业务计算混杂:金额计算、状态判断全塞在一起
  3. 统计与分页处理共存:一个方法既要统计又要分页
  4. 缺乏抽象层次:所有逻辑都平铺在一个方法中

是时候动手重构了!

第一步:拆分查询构建器

我首先创建了OrderQueryBuilder,专门负责处理那30多个查询条件:

class OrderQueryBuilder
{
    private $query;
    private $params;

    public function __construct(array $params)
    {
        $this->query = Order::query();
        $this->params = $params;
    }

    // 核心方法:应用所有过滤器
    public function applyFilters()
    {
        // 状态筛选
        if ($status = $this->getParam('status')) {
            $this->applyStatusFilter($status);
        }
        
        // 时间范围筛选
        if ($startTime = $this->getParam('startTime')) {
            $this->query->where('created_at', '>=', $startTime);
        }
        
        // 支付渠道筛选
        if ($payChannel = $this->getParam('pay_channel')) {
            $this->applyPayChannelFilter($payChannel);
        }
        
        // 还有30多个条件...
        // 每个条件都有自己的处理方法
        
        return $this->query;
    }

    // 支付渠道筛选逻辑
    private function applyPayChannelFilter($payChannel)
    {
        $channelMap = [
            1 => fn() => $this->query->where('is_alipay', 1),
            2 => fn() => $this->query->where('is_wx', 1),
            3 => fn() => $this->query->where('is_card', 1),
            // 更多映射关系...
        ];

        if (isset($channelMap[$payChannel])) {
            $channelMap[$payChannel]();
        } else {
            $this->query->where('pay_type', $payChannel == 1 ? 0 : 1);
        }
    }

    // 状态筛选逻辑
    private function applyStatusFilter($status)
    {
        $statusHandlers = [
            Order::STATUS_CLOSED => fn() => $this->query->whereNotNull('closed_at'),
            Order::STATUS_COMPLAINT => fn() => $this->applyComplaintFilter(),
            // 其他状态处理...
        ];

        if (isset($statusHandlers[$status])) {
            $statusHandlers[$status]();
        }
    }

    private function getParam($key)
    {
        return $this->params[$key] ?? null;
    }
}

第二步:创建数据处理器

订单数据需要各种计算和转换,我创建了OrderDataProcessor

class OrderDataProcessor
{
    // 处理订单集合
    public function processCollection($orders)
    {
        foreach ($orders as $order) {
            $this->processSingleOrder($order);
        }
    }

    // 处理单个订单
    public function processSingleOrder($order)
    {
        // 具体的处理逻辑
        $order->total_amount_yuan = fenToYuan($order->total_amount);
        $order->status_text = $this->getStatusText($order->status);
        // 更多处理...
    }

    
    
}

第三步:重组Repository

现在我可以优雅地重组原来的巨无霸方法:

class OrderRepository
{
    protected $dataProcessor;

    public function __construct(OrderDataProcessor $dataProcessor)
    {
        $this->dataProcessor = $dataProcessor;
    }

    public function getAdminIndex($params, $isTj = false)
    {
        // 构建查询
        $query = (new OrderQueryBuilder($params))->applyFilters();
        
        // 统计分支
        if ($isTj) {
            $sum = $query->sum('total_amount');
            $fee = $query->sum('fee');
            $count = $query->count();
            
            return [
                'sum' => fenToYuan($sum),
                'count' => $count
            ];
        }
        
        // 分页查询
        $orders = $query->orderBy('id', 'desc')->paginate(10);
        
        // 数据处理
        $this->dataProcessor->processCollection($orders->getCollection());
        
        return $orders;
    }
}

我收获的经验

  1. 不要被模式名称束缚:Repository模式不是把数据库操作堆在一起的理由

  2. 单一职责是底线:一个方法超过50行就应该警惕,超过100行就值得重构

  3. 拆分策略很重要

    • 按职责拆分:查询构建、数据处理、业务逻辑
    • 按变化频率拆分:经常变的和稳定的逻辑分开
  4. 测试变得容易:现在我可以单独测试查询构建器、数据处理器和Repository

  5. 代码可读性大幅提升:新同事能够快速理解代码结构

还能做得更好

虽然重构取得了成效,但还有优化空间:

  1. 进一步抽象查询条件:每个查询条件都可以成为独立对象

  2. 引入缓存机制:频繁调用的统计结果可以缓存

  3. 异常处理增强:当前版本错误处理还不够完善

重构前的代码:

  • 一个400行的巨方法
  • 难以测试和维护
  • 新同事看得头晕眼花

重构后的代码:

  • 多个专注单一职责的类
  • 平均每个方法20行左右
  • 易于测试和理解

重构不是一次性的工程,而是一种持续改进的心态。每当我要往一个方法里添加新功能时,都会先问自己:这个职责是否已经有人承担了?如果没有,是不是应该创建一个新的组件?

结语

希望我的实战经验对你有启发。如果你的代码库中也有这样的“巨无霸”,不妨尝试用类似的方法拆解它。记住:好的代码不是一开始就设计出来的,而是在不断重构中演化出来的。