编程

减少代码重复

1116 2023-02-03 08:06:26

代码重复是很多开发者最为头疼的事情,你以为你已经解决了这一问题,但是还是难免会在实践中碰到这一问题。

作为 Laravel 开发者,在我所见的许多代码库中,控制台命令似乎是常被遗忘的区域。

本教程中,我将带你一起关注如何在代码编写中减少代码重复。假设我们有一个 Laravel 线上商店,有一天我们需要生成有关所有订单的销量及状态的报告。当前的方式是,登录后台面板,点击按钮生成报告。

这可能是不现实的,因为这里的第一个例子是将其自动化。但是,在我们冒险通过这个想法来改进它的过程中,请先坚持我的观点。

第一步便是创建一个或一系列 artisan 命令来生成报告。将命令明确命名为我们想要实现的目标是有意义的。因此,让我们从销售数据开始:

final class SalesFigures extends Command
{
    public $signature = 'reports:sales';
 
    public $description = 'Run a daily report on sales.';
 
    public function handle(): int
    {
        $date = now()->subDay();
 
        $sales = Order::query()
            ->where('status', Status::COMPLETE)
            ->whereBetween(
                'completed_at',
                $date->startOfDay(),
                $date->endOfDay(),
            )->latest()->get();
 
        // send information through to the report builder
    }
}

此处我们有了一个简单的命令,可以用于运行获取昨天被标记为完成的销售数据。查询本身很简单,查询状态以及日期为昨日的订单,并进行排序使最新的排在最前面,以便我们建立一个按时间顺序排列的报告。

尽管如此,我们要如何改进呢?在应用的其他地方,我们是否需要以类似的顺序获得这些订单?让我们开始重构过程。

首先,这一查询我们会在一些不同的区域中运行。因此我们可以将其移到它自己的类中,供我们后续调用运行。

final class ResultsForPeriod implements ResultsForPeriodContract
{
    public function handle(
        Builder $query,
        CarbonInterface $start,
        CarbonInterface $end,
    ): Builder {
        return $builder->whereBetween(
            'completed_at',
            $start,
            $end,
        );
    }
}

这将允许我们在任何模型中获得特定时间跨度的结果,这对项目更有利。

final class SalesFigures extends Command
{
    public $signature = 'reports:sales';
 
    public $description = 'Run a daily report on sales.';
 
    public function handle(ResultsForPeriodContract $query): int
    {
        $date = now()->subDay();
 
        $sales = $query->handle(
            query: Order::query()
                ->where('status', Status::COMPLETE),
            start: $date->startOfDay(),
            end: $date->endOfDay()
        )->latest()->get();
 
        // send information through to the report builder
    }
}

我们已经实现了我们为基于时间跨度进行过滤而构建的查询。我们还能从何处着手,使其更清洁、更高效?我们可以创建一个特定的服务来处理这个报告方面吗?这项服务在其他方面也有帮助吗?

我们的电子商务仪表盘可能会从这些报告中获得一些信息,因此一些代码重用已经到位。让我们将其移至服务。

final class ReportService implements ReportServiceContract
{
    public function __construct(
        private readonly ResultsForPeriodContract $periodFilter,
    ) {}
 
    public function dailySales(CarbonInterface $start, CarbonInterface $end): Collection
    {
        return $this->periodFilter->handle(
            query: Order::query()->where('status', Status::COMPLETE),
        )->latest()->get();
    }
}

现在我们可以将其移回到 artisan 命令中了:

final class SalesFigures extends Command
{
    public $signature = 'reports:sales';
 
    public $description = 'Run a daily report on sales.';
 
    public function handle(ReportServiceContract $service): int
    {
        $date = now()->subDay();
 
        $sales = $service->dailySales(
            query: Order::query(),
            start: $date->startOfDay(),
            end: $date->endOfDay()
        );
 
        // send information through to the report builder
    }
}

如你所见,我们有了一个新的干净整洁的命令,它很好地利用了与应用程序其他领域共享的代码。