1. 项目背景与挑战
三年前接手这个PHP老项目时,我差点被代码库的状态吓退。这是一个运行了8年的电商系统,核心业务每月处理数百万订单,但技术债务已经堆积到危险边缘。首次打开代码目录时,我看到的是:
- 超过200个PHP文件直接放在根目录下
- 混合了至少三种不同编码风格的SQL查询
- 核心业务逻辑分散在十几个"utils_"开头的文件中
- 完全没有单元测试覆盖
- 每次部署需要手动同步20多个配置文件
最致命的是,系统还在持续添加新功能。就像给危房加盖楼层,每次改动都伴随着未知风险。记得有次修改支付接口,引发了库存同步的异常,导致线上线下库存数据不一致,最终需要人工核对三天才恢复。
2. 重构策略与路线图
2.1 建立安全网
在开始任何实质性重构前,我花了两个月搭建测试基础设施:
-
接口测试覆盖:用PHPUnit为所有公开API添加基础测试
php复制class OrderControllerTest extends TestCase { public function testCreateOrder() { $response = $this->post('/api/order', [ 'product_id' => 101, 'quantity' => 2 ]); $response->assertStatus(201); $this->assertDatabaseHas('orders', [ 'total_amount' => 198.00 ]); } } -
数据库迁移工具:将手工SQL脚本转为可回滚的迁移文件
bash复制
php artisan make:migration add_index_to_order_table --table=orders -
监控告警系统:用Prometheus+Grafana监控关键指标
重要提示:测试覆盖率从0%提升到40%的过程中,我们发现了17个隐藏的业务逻辑错误。这证明了即使不修改代码,仅添加测试就能暴露问题。
2.2 渐进式架构改造
采用"绞杀者模式"逐步替换老旧组件:
-
路由层改造:
- 保留原有.php入口文件
- 新增Laravel路由转发旧请求
php复制Route::get('/legacy/product.php', function() { include base_path('legacy/product.php'); }); -
依赖解耦:
- 用DI容器包装全局变量
php复制$container->singleton('db', function() { return LegacyDB::getInstance(); }); -
数据层分离:
- 逐步将SQL查询迁移到Eloquent模型
- 使用读写分离模式兼容旧代码
3. 关键技术攻坚
3.1 会话系统改造
原系统使用自定义会话机制,存在严重的安全隐患:
php复制// 旧代码示例
$_SESSION['user'] = $db->query("SELECT * FROM users WHERE id=".$_GET['uid']);
改造方案:
- 实现会话迁移中间件
- 双写新旧会话数据
- 验证无状态后移除旧系统
3.2 支付流程重构
支付模块有超过10个状态判断分支:
php复制if ($order['status'] == 1 && $payment['type'] == 'alipay') {
// 200行逻辑
} elseif (...) {
// 更多嵌套判断
}
重构为状态机模式:
php复制class PaymentProcessor {
public function handle(PaymentContext $context) {
$this->state->process($context);
}
}
4. 性能优化实践
4.1 懒加载优化
发现商品详情页执行了N+1查询:
php复制foreach ($categories as $cat) {
$products = $db->query("SELECT * FROM products WHERE cat_id=".$cat['id']);
// ...
}
解决方案:
- 预加载关联数据
- 实现批量查询接口
- 添加缓存层
优化后API响应时间从1200ms降至280ms。
4.2 前端资源改造
将分散的jQuery插件整合为Webpack打包:
- 保留原有ID选择器兼容性
- 按需加载非核心JS
- 实现CSS模块化
5. 持续改进机制
5.1 代码质量门禁
在CI流程中添加:
- PHPStan静态分析
- 代码复杂度检查
- 自动格式化校验
5.2 文档自动化
用Swagger生成API文档:
php复制/**
* @OA\Get(
* path="/api/products",
* @OA\Response(response="200", description="产品列表")
* )
*/
class ProductController {
// ...
}
6. 经验总结
- 测试优先原则:没有测试覆盖的重构等于破坏
- 小步快跑:每次提交只做一件事,降低回滚成本
- 指标驱动:用性能监控数据证明改进效果
- 团队协作:建立代码审查文化,分享重构模式
这个项目教会我最重要的经验是:老系统改造不是技术问题,而是风险管理艺术。我们最终用18个月时间,在不中断业务的情况下完成了架构升级,错误率下降76%,新功能交付速度提升3倍。