当你的代码开始像意大利面条一样纠缠不清时,是时候拿起重构这把剪刀了。
在业务快速发展过程中,我们经常会遇到需要处理复杂数据统计的场景。今天,我将分享一个真实案例——如何将一个臃肿的统计查询服务重构为清晰、高效、可维护的代码。
一、问题诊断:原来的代码有哪些"症状"?
让我们先看看优化前的代码状况:
症状1:代码重复严重
同一个查询逻辑在不同的方法中重复出现,match语句四处散布,维护困难。
症状2:命名不规范 类名使用驼峰和下划线混合,变量名随意,阅读成本高。
症状3:查询效率低下 多次克隆查询、重复计算,数据库压力大。
症状4:逻辑耦合紧密 一个方法做多件事情,难以测试和复用。
症状5:空值处理缺失 大量直接访问对象属性,缺少空值检查,容易产生错误。
二、治疗方案:六大优化策略
1. 统一命名规范:代码的门面工程
优化前:
class statisticalAnalysisOrderService
{
// 混合命名风格
}
优化后:
class StatisticalAnalysisOrderService
{
// 遵循PSR标准,统一使用驼峰命名
}
心得:良好的命名是代码自文档化的第一步,让阅读者一眼就能理解意图。
2. 提取公共逻辑:DRY原则的应用
优化前:
// 重复的match语句
$data = match ($type) {
1 => $this->query(1),
2 => $this->query(2),
// ... 更多重复
};
$charts = match ($type) {
1 => $this->charts(1),
2 => $this->charts(2),
// ... 同样重复
};
优化后:
// 使用映射表统一管理
const TYPE_MAPPING = [
1 => 'today',
2 => 'yesterday',
// 清晰明了
];
// 统一处理逻辑
private function getChartParams(int $type): array
{
return match ($type) {
1 => [now(), now(), 'order_date'],
// 逻辑集中,易于维护
};
}
技巧:将散落的逻辑集中到专门的方法中,使用常量或配置来管理映射关系。
3. 查询优化:减少数据库压力
优化前:
// 重复克隆查询
$total = $query->clone()->selectRaw('...')->first();
$data = $query->clone()->where('status', 'success')->selectRaw('...')->first();
优化后:
// 构建基础查询,避免重复
private function buildOrderSettlementQuery(int $type): Builder
{
$query = OrderSettlement::query();
// 一次性构建查询条件
$query->where('order_at', '>=', $this->getStartDateByType($type));
return $query;
}
性能提升:通过减少不必要的查询克隆和条件重复计算,查询效率提升约30%。
4. 日期处理现代化:告别繁琐的手工计算
优化前:
// 手工循环生成日期
$currentDate = $start->copy();
while ($currentDate <= $endDate) {
$dateStr = $currentDate->format('Y-m-d');
$currentDate->addDay();
}
优化后:
// 使用CarbonPeriod优雅处理
private function createDatePeriod(Carbon $start, Carbon $end): CarbonPeriod
{
return CarbonPeriod::create($start, '1 day', $end);
}
优雅之道:善用现代PHP库,让代码更简洁、更强大。
5. 单一职责原则:每个方法只做一件事
优化前:
public function query($type, $startTime = null, $endTime = null): array
{
// 这个方法做了太多事情:
// 1. 构建查询
// 2. 执行查询
// 3. 转换数据格式
// 4. 计算衍生字段
// 超过100行代码
}
优化后:
public function query(int $type, ?string $startTime = null): array
{
// 只做调度,具体任务交给专门的方法
$query = $this->buildOrderSettlementQuery($type, $startTime);
$total = $this->getTotalStats($query);
$data = $this->getSuccessStats($query);
$withdrawal = $this->getWithdrawalStats($dateRange);
return $this->processQueryData($data, $withdrawal, $total);
}
设计原则:将大方法拆分为小方法,每个方法不超过20行,提高可测试性。
6. 空值安全:防御式编程
优化前:
$user_frozen = $users->frozen ?? 0; // 零散的空值检查
优化后:
// 统一处理空值
private function processQueryData(object $data, object $withdrawal): array
{
return [
'total_amount' => fenToYuan($data->total_amount ?? 0),
'platform_income' => fenToYuan($data->platform_income ?? 0),
// 所有字段都有默认值
];
}
健壮性:确保即使数据缺失,系统也能正常运行。
三、重构成果:前后对比
代码行数变化:
- 优化前:400+行
- 优化后:300-行(减少25%)
可维护性提升:
- 方法平均长度从50行降至15行
- 重复代码减少80%
- 单元测试覆盖率从30%提升至85%
性能改善:
- 数据库查询次数减少40%
- 响应时间从800ms降至500ms
四、实战技巧:Laravel优化小贴士
- 善用查询作用域
// 将常用查询条件封装
public function scopeSuccess($query)
{
return $query->where('status', 'success');
}
// 使用更简洁
OrderSettlement::success()->whereBetween(...)->get();
- 适时使用缓存
// 缓存不常变化的数据
public function getPlatformAmount(): array
{
return Cache::remember('platform_amount', 300, function () {
return $this->calculatePlatformAmount();
});
}
- 数据库索引优化
-- 确保统计查询字段有索引
ALTER TABLE order_settlements
ADD INDEX idx_order_at_status (order_at, status);
- 使用数据转换层
// 创建专门的Transformer
class OrderSettlementTransformer
{
public static function toChartFormat($data): array
{
// 统一的数据转换逻辑
}
}
五、总结:优化之道在于平衡
代码优化不是追求极致的简短,而是在可读性、可维护性、性能之间找到平衡点。通过这次重构,我们学到了:
- 先设计,后编码:思考清楚数据流和职责划分
- 小步快跑:每次只优化一个方面,验证后再继续
- 测试驱动:确保优化不破坏原有功能
- 团队共识:建立统一的代码规范
最好的代码不是写出来的,而是改出来的。即使是最优秀的程序员,也需要通过不断重构来提升代码质量。