如何重构遗留 PHP 代码 不至于崩溃

如何重构遗留 PHP 代码 不至于崩溃

当你意识到自己接手了一坨遗留代码

通常是这样开始的。

有人让你修一个小 bug,可能是个验证问题,可能是个只在生产环境出现的奇怪边界情况。你打开文件,想着很快就能搞定。

然后你看到了:

  • 一个文件 3000 行
  • 业务逻辑和 HTML 混在一起
  • 循环里面写数据库查询
  • 变量名叫 $x$temp$data2
  • 函数的返回类型取决于代码当时的心情

这时候你才意识到:这就是遗留 PHP 代码。

不是"老"PHP,不是"烂"PHP,只是活得够久、变成了核心系统的代码。

现在它归你了。

重构遗留 PHP 的名声一直不好------痛苦、高风险、精神损耗大。但大部分痛苦来自重构的方式,而不是代码本身。

这篇文章讲的是怎么重构遗留 PHP,同时保持心态不崩、系统不炸、也不用推翻重来。

先搞清楚"遗留代码"到底是什么意思

遗留代码不等于"烂代码"。

遗留代码是没有安全网的代码:

  • 没有测试
  • 没有清晰的边界
  • 没有文档
  • 改了之后心里没底,不知道会不会炸

有些遗留 PHP 是 15 年前写的。有些是去年写的------赶工期,没时间收拾。

怪过去没有用。你的任务不是评判,而是让下一次改动比上一次更安全。

光是这个心态转变,就能改变很多事。

黄金法则:永远不要盲目重构

在动结构、格式、架构之前,你需要的是可见性。

不理解行为就动手重构,是线上事故的典型成因。

找到关键路径

先问这几个问题:

  • 哪些接口被调用得最频繁?
  • 哪些脚本涉及资金、认证或数据完整性?
  • 哪些定时任务在自动运行?

从这些地方开始,而不是从最丑的那个文件开始。

遗留 PHP 系统通常能活下来,是因为核心路径是稳定的------哪怕代码很难看。

改代码之前,先锁住行为

你不需要完整的测试覆盖率,你需要的是行为锚点。

特征测试(Characterization Tests)

不是测代码"应该"做什么,而是测它"目前"在做什么。

PHPUnit 示例:

php 复制代码
public function testLegacyDiscountCalculation()
{
    $calculator = new LegacyDiscountCalculator();
    $result = $calculator->calculate(100, 'VIP');
    $this->assertEquals(85, $result);
}

你可能不喜欢这个逻辑,可能还没看懂。没关系。

目标很简单:如果我重构了这段代码,我要能知道行为变了没有。

这些测试是临时的安全带------但关键时刻能救命。

先止血

在"清理"之前,先修掉那些正在持续制造问题的东西。

遗留 PHP 常见的出血点

全局状态

php 复制代码
global $db;
global $user;

全局变量让重构几乎不可能。第一步:把它包起来。

php 复制代码
class Database
{
    public function query(string $sql): array
    {
        return $GLOBALS['db']->query($sql);
    }
}

不完美------但你有了一条缝(seam)。

职责混杂

php 复制代码
function processOrder()
{
    // validate input
    // query database
    // calculate totals
    // send email
    // render HTML
}

不要一口气全改,一次只抽离一个职责。

先建边界,再追求完美

重构不是重写。

目标是划出清晰的边界,而不是打磨漂亮的内部实现。

用有意义的名字提取函数

遗留代码往往做的事情是对的,只是形式不对。

php 复制代码
$total = 0;
foreach ($items as $item) {
    if ($item['type'] === 'digital') {
        $total += $item['price'];
    } else {
        $total += $item['price'] + 10;
    }
}

第一步重构:

php 复制代码
$total = calculateOrderTotal($items);
function calculateOrderTotal(array $items): float
{
    $total = 0;
    foreach ($items as $item) {
        $total += itemTotal($item);
    }
    return $total;
}

行为没变,但可读性和可测试性变了。

这就是一次有效的改进。

在最痛的地方加类型

不需要第一天就全面开启严格类型。

从 bug 容易藏身的地方开始:

  • 函数输入
  • 函数输出
  • 公开方法
php 复制代码
function findUser($id)
{
    // returns array or false or null
}

重构方向:

php 复制代码
function findUser(int $id): ?User
{
    // return User or null
}

类型做两件事:表达意图暴露隐藏的假设

遗留 PHP 能跑通,往往是因为什么都是"灵活"的。类型会逼你面对真实情况。

用有意义的表达替换魔术数字

遗留 PHP 特别喜欢用标志位。

php 复制代码
if ($status === 1) {
    // active
} elseif ($status === 2) {
    // pending
}

用常量或枚举重构:

php 复制代码
enum UserStatus: int
{
    case Active = 1;
    case Pending = 2;
}

然后:

php 复制代码
if ($user->status === UserStatus::Active) {
    // ...
}

代码一下子就能自己解释自己了。

慢慢消灭复制粘贴的代码

重复代码是生存策略,不是无能的表现。

遗留 PHP 之所以有大量重复,是因为当时做抽象的风险太大。

你的做法:

  1. 找到稳定的重复
  2. 搞清楚差异之后再提取

例如这段代码到处都是:

php 复制代码
$tax = $price * 0.1;
$total = $price + $tax;

提取出来:

php 复制代码
function calculateTax(float $price): float
{
    return $price * 0.1;
}

不要过早做过度抽象。重构的方向是清晰,不是理论上的复用。

把基础设施和业务逻辑分开

遗留 PHP 重构中收益最大的一件事,就是把下面这些东西和真正的业务规则分开:

  • SQL
  • HTTP
  • 框架胶水代码

重构前:

php 复制代码
$result = mysqli_query($conn, "SELECT * FROM users WHERE id = $id");
if ($row['type'] === 'premium') {
    $discount = 0.2;
}

重构后(渐进式):

php 复制代码
$user = $userRepository->findById($id);
$discount = $discountPolicy->forUser($user);

即使 repository 内部仍然用的是裸 SQL,你也已经创造了一条缝。

有缝,重构才安全。

不要一次性"升级"所有东西

在遗留系统中升级 PHP 版本是件让人紧张的事,因为升级会把隐藏的问题暴露出来。

把升级当成反馈,而不是失败:

  • 遇到废弃警告就修
  • 在非生产环境开启 warning
  • 把 notice 当成重构线索

每一条警告都在告诉你:"代码的这个部分不够清晰,或者不够安全。"

这是有价值的信息。

用日志当临时调试网

测试缺失的时候,日志能给你信心。

php 复制代码
logger()->info('Calculating discount', [
    'user_id' => $user->id,
    'type' => $user->type,
]);

重构之后对比日志。如果行为出现了意料之外的偏差,你能及时发现。

切薄片来重构

永远不要一次性重构:

  • 整个模块
  • 整个功能
  • 整个目录

一次改一条路径、一个函数、一个职责。

小改进会累积。

遗留系统不是瞬间崩塌的------它是慢慢被侵蚀的。你的重构也应该以同样的节奏推进。

知道什么时候该停手

完美代码是个陷阱。

以下情况就该收手了:

  • 下一次改动让你觉得有安全感
  • 代码意图是清晰的
  • 各部分有明确的边界
  • 你能向另一个开发者讲清楚这段代码

你不是在创作艺术品,你是在降低风险。

重构遗留 PHP 的情感面

重构遗留 PHP 之所以让人难受,是因为:

  • 代码在跟你对着干
  • 问题一眼就能看到
  • 系统还依赖着这些代码在跑

但别忘了:这些代码能活到今天,说明它一直在发挥作用。

你的任务不是抹掉过去,而是让未来没那么痛苦。

最后

重构遗留 PHP 不需要英雄主义。

它需要的是:

  • 耐心
  • 克制
  • 对历史约束的理解
  • 在小改进上的持续纪律

你不是通过重写来重构遗留 PHP 的。你是通过一次又一次安全的小改动,一点一点赢得它的信任。

如果做对了------你不会崩溃。说不定还会觉得挺有意思。 如何重构遗留 PHP 代码 不至于崩溃

相关推荐
墨染青竹梦悠然2 小时前
基于Django+vue的单词学习平台
前端·vue.js·后端·python·django·毕业设计·毕设
Victor3562 小时前
MongoDB(11)MongoDB的默认端口号是多少?
后端
Victor3562 小时前
MongoDB(10)如何安装MongoDB?
后端
野犬寒鸦2 小时前
缓存与数据库一致性的解决方案:实际项目开发可用
java·服务器·数据库·后端·缓存
JaguarJack2 小时前
PHP 的问题不在语言本身,而在我们怎么写它
后端·php·服务端
jdbcaaa4 小时前
Go 语言 runtime 包的使用与注意事项
开发语言·后端·golang·runtime
ZHOUPUYU6 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
青云计划11 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor35611 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端