写完 Service 方法却忘了挂上去:死代码陷阱与集成检查清单
有时候 bug 不在代码里,而在"这段代码根本没有被执行"。我在一个后台管理系统的迭代中踩过这个坑:退款流程里的优惠券退还逻辑实现得相当完整——事务、行锁、状态判断一个不少,却在生产环境里对用户毫无效果。排查半天才发现:CouponService::returnCoupon() 从来没有在任何业务路径上被调用。
现象
消息模板发送和优惠券退还两个功能上线后,运营反馈消息没收到、退款后优惠券也没回来。翻日志,相关 Service 方法的日志一条都没有。代码层面单测全绿,逻辑完全正确。
用 grep 搜一下方法调用点:
grep -rn "returnCoupon" app/ --include="*.php" | grep -v "function returnCoupon"输出为空。方法定义了,但 0 个调用点。
根因
开发任务的描述是「实现 CouponService::returnCoupon()」,而不是「在退款成功的所有路径上调用退还逻辑」。任务的语义边界停在了方法实现,没有延伸到集成。
这在多人协作或 AI 辅助开发中尤其容易发生——执行者忠实完成了「写方法」这件事,但「接入业务节点」是另一件隐含的事,没有被拆出来、没有被验收。
单测覆盖的是方法内部逻辑,不能证明这个方法在业务流程中被调用。一个 0 调用点的方法可以有 100% 的单测覆盖率,但它对用户而言等于不存在。"写方法"和"挂进业务流"是两个独立的任务,必须分别验收。
正解
任务拆分时就定义调用点
每个新 Service 方法在任务阶段就要列出它的调用点,和方法实现作为一个整体交付:
任务:实现 CouponService::returnCoupon()
调用点(必须同步完成):
- RefundService::refundToBalance() — 退款到余额成功后
- RefundService::refundOffline() — 线下退款审核通过后
- RefundService::refundToWechat() — 退款到微信支付成功后
集成模式:事务外 + try-catch
退还优惠券、发消息这类副作用,调用应放在主事务之外,失败只记日志不回滚主流程:
// 主事务已提交,以下为副作用,失败不影响退款本身
try {
(new CouponService())->returnCoupon($orderId, $userId);
} catch (\Throwable $e) {
Log::warning('优惠券退还失败: ' . $e->getMessage());
}验收阶段:死代码检测
提测前用一条 grep 命令确认集成:
# 方法名替换为实际方法
grep -rn "returnCoupon" app/ --include="*.php" | grep -v "function returnCoupon"结果为空 = 死代码,必须补调用点再提测。
把「有哪些业务节点要调用这个方法」作为任务描述的一部分写清楚,验收时用 grep 确认调用点不为零。这两步加起来 5 分钟,能把死代码问题在源头消灭。
| Service 方法 | 典型调用点 | |-------------|-----------| | 消息模板发送 | 支付成功、接单、完成、售后审核通过、退款成功 | | 优惠券退还 | 退款成功的所有路径 | | 积分变动 | 下单、签到、分享、退款 | | 统计数据更新 | 订单状态变更、评价提交 |
一句话外卖
写一个 Service 方法,任务完成度只有 50%;另外 50% 是把它挂进每一条应该触发它的业务路径。