
本文介绍如何将一个职责混杂的 getTotalProductsNumber() 方法拆分为多个高内聚、低耦合的私有方法,提升代码可读性、可维护性与单元测试友好性。
本文介绍如何将一个职责混杂的 `gettotalproductsnumber()` 方法拆分为多个高内聚、低耦合的私有方法,提升代码可读性、可维护性与单元测试友好性。
原始方法虽功能明确(统计发票关联商品总数量),但存在明显设计问题:紧耦合数据库构造逻辑、重复处理查询构建细节、类型转换与业务逻辑混杂。通过职责分离,我们可将其重构为三层清晰结构:入口协调层 → 业务语义层 → 查询执行层。
✅ 重构后代码(推荐写法)
public function getTotalProductsNumber(): int
{
$dataset = 'supplier_invoice_products INNER JOIN supplier_invoices AS si USING (supplier_invoice_id)';
$extras = $this->getConditionsAndOptions();
$queryBuilder = [
'dbm' => new Dbm_Supplier($dataset),
'where' => $extras['where'],
'opt' => $extras['opt']
];
return $this->sumOfProductQuantities($queryBuilder);
}
private function sumOfProductQuantities(array $queryBuilder): int
{
$select = 'SUM(product_quantity) AS sumTotal';
$row = $this->queryBuilderFirstRow($queryBuilder, $select);
return (int)($row['sumTotal'] ?? 0); // 安全兜底:空结果返回 0
}
private function queryBuilderFirstRow(array $qb, string $select): array
{
return $qb['dbm']->findFirstSimple($qb['where'], $select, $qb['opt']);
}
? 关键改进点说明
-
单一职责明确:
- getTotalProductsNumber() 仅负责组装上下文并委托;
- sumOfProductQuantities() 表达业务意图(“求商品总数”),屏蔽 SQL 细节;
- queryBuilderFirstRow() 封装底层查询调用,便于统一日志、异常处理或 Mock 测试。
增强健壮性:
使用 ?? 0 替代强制类型转换 (int)$row['sumTotal'],避免因 $row 为空或键缺失导致 Notice 或致命错误。提升可测性:
私有方法虽不可直接单元测试,但可通过 @coversPrivate 注解或提取为 protected 方法配合测试双刃剑(如使用 ReflectionClass)验证逻辑;更重要的是,queryBuilderFirstRow 可被模拟(Mock),使 sumOfProductQuantities 成为纯逻辑函数。
⚠️ 注意事项与延伸建议
- 若项目已引入 Doctrine ORM 或 Laravel Eloquent,强烈建议迁移至 Invoice::withCount('products') 或 InvoiceProduct::where(...)->sum('product_quantity') 等声明式写法,彻底解耦 SQL 拼接。
- Dbm_Supplier 类若支持链式查询构建器(如 ->where()->select()->first()),应优先采用,进一步消除数组传递的隐式契约。
- getConditionsAndOptions() 返回结构需稳定(确保始终含 'where' 和 'opt' 键),建议添加断言或类型提示(如 array{where: string, opt: array})提升 IDE 支持与早期报错能力。
重构不是终点,而是让代码更贴近领域语言、更易随业务演进的起点。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











