CatchAdmin PHP 后台管理框架 Logo CatchAdmin

7 个一眼看出仓促写成的 PHP 代码异味

设想一名开发者刚加入一家新公司。第一个任务是在代码库里修复一个小 bug。打开文件,开始阅读,三十秒内便感到不安。

真正令人不安的是 bug 周围的代码。

一个 400 行函数。名为 $data$temp$result2 的变量。循环里的数据库查询。只用 die() 处理错误。还有一句没有日期、没有作者的注释:// TODO: fix this later

bug 只有三行。周围代码却需要三天才能理解到可以安全修改的程度。

这就是代码异味在实践中的样子。代码异味通常表现得很安静:没有崩溃,也没有报错,只留下压力下决策沉积出的维护成本。凌晨两点看似合理的捷径,最终在代码库中留下长期损伤,让后来者不得不承受。

下面是 7 个最常见的 PHP 代码异味,它们都在传递同一个信号:“这段代码写得很赶”。文章会说明它们长什么样、为什么会出现,以及应该如何改写。

TL;DR 快速版

代码异味是让代码更难阅读、测试、维护和信任的模式。

大多数 PHP 代码异味形成于时间压力之下,并因为无人回头清理而长期存在。

本文的 7 个模式覆盖命名、架构、错误处理和查询逻辑等多个层面。

每一种异味都有具体修复方式,并且都不需要完整重写。

目标是让下一位开发者,或未来的自己,能够直接读懂代码。

本文将介绍

  • 生产代码库中最常见的 7 个 PHP 代码异味
  • 每种模式为什么会形成,通常来自什么压力
  • 修复每一种异味的具体重构方式
  • 如何在代码审查中于合并进主分支前识别它们
  • 如何从“能跑”转向“可维护”的思维方式

关于代码异味与 bug

代码异味本身属于维护风险信号。代码可以运行,测试可以通过,部署可以成功。然后六个月后,有人打开这个文件添加功能,在真正修改前先花两个小时理解它到底在做什么。

这就是代码异味造成的损害:它集中发生在维护阶段。维护阶段占据了软件项目生命周期的大部分。

下面这些异味来自真实公司的真实 PHP 代码库,出自处在压力之下的开发者之手。识别它们,是阻止它们继续扩散的第一步。

异味 1:什么都做的函数

设想打开一个名为 processOrder() 的函数,它有 300 行。它校验输入、查询数据库、应用折扣逻辑、发送确认邮件、更新库存、记录交易日志,并格式化响应。

它什么都做,也就意味着它对一切负责。结果是,一旦某处出问题,开发者很难知道该从哪里查起。

php
// 异味
function processOrder(array $data): array
{
    // 20 行校验
    // 40 行数据库查询
    // 30 行折扣计算
    // 25 行邮件发送
    // 20 行库存更新
    // 30 行日志记录
    // 15 行响应格式化
    // ... 300 行之后
}

这类函数通常被称为 God Function,也就是知道太多、做得太多的函数。它几乎无法单元测试,因为它承担十几个职责,还触碰了半个基础设施。修改它也很危险,因为折扣逻辑的改动可能距离邮件发送代码相隔三屏。

修复方式:单一职责。每个函数应该完整做好一件事。

php
function processOrder(array $data): OrderResult
{
    $validated = $this->validator->validate($data);
    $order     = $this->repository->createOrder($validated);
    $discount  = $this->discountEngine->apply($order);
    $this->mailer->sendConfirmation($order);
    $this->inventory->decrement($order);
    $this->logger->logTransaction($order);
    
    return new OrderResult($order);
}

现在,processOrder() 是一个编排器。每个协作者完成一项工作。每个协作者都可以独立测试。邮件发送失败时,定位位置非常明确。

为什么会出现:截止日期。把新功能加进现有函数,比设计一个新抽象更快。第一版 50 行时看起来还好。经过上百次迭代后,它变得难以维护,而冲刺过程中很少有人愿意停下来重构。

需要警惕:函数中有多层缩进、很长的参数列表,以及用注释把函数拆成“区块”,这些都说明可能正在面对一个试图隐藏自己的 God Function。需要用区块注释解释函数在做什么时,这些区块通常应该成为独立函数。

异味 2:只描述类型、缺少用途的变量名

设想在一个函数里看到 $data$result$temp$arr$obj$val$item,而且它们都在同一个代码块中。读者花在追踪变量内容上的时间,超过了理解逻辑本身。

php
// 异味
function calculate($data)
{
    $result = [];
    foreach ($data as $item) {
        $temp = $item['price'] * $item['qty'];
        if ($temp > 100) {
            $result[] = $temp;
        }
    }
    return $result;
}

$data 是什么数据?什么列表?$result 保存什么?$temp 表示什么?这些可以被反推出来,但读代码的人本不该承担这种成本。

修复方式:按照变量在业务领域中代表的内容命名。

php
function calculateBulkOrderTotals(array $orderLines): array
{
    $qualifyingTotals = [];

    foreach ($orderLines as $line) {
        $lineTotal = $line['price'] * $line['quantity'];
        if ($lineTotal > 100) {
            $qualifyingTotals[] = $lineTotal;
        }
    }
    return $qualifyingTotals;
}

逻辑相同,可读性完全不同。$orderLines$lineTotal$qualifyingTotals 让任何开发者都能直接理解业务意图。

为什么会出现:速度。快速编写代码时,开发者会抓住脑海中第一个变量名。$data 总能用。$result 总“说得过去”。随后这些名字进入生产、进入 git 历史,也进入每个维护者的认知负担。

经验规则:如果一个变量名可以描述任何程序中的任何变量,它就不够具体。$userEmailAddresses 是变量名。$data 是占位符。

需要警惕:紧凑循环计数器($i$j)和数学上下文($x$y)之外的单字母变量。在 PHP 应用代码中,排序比较器之外几乎没有理由使用 $a$b

异味 3:循环中的数据库查询

这种异味不只难闻,还会在规模增长时主动拖垮性能。N+1 查询问题是 PHP 应用中最常见、代价最高的模式之一,并且几乎总是来自仓促写成的代码。

设想正在渲染订单列表,并显示每个客户的姓名。直觉上很简单:

php
// 异味:N+1 查询
$orders = $db->query("SELECT * FROM orders")->fetchAll();

foreach ($orders as $order) {
    // 每一条订单都会触发一次新查询
    $customer = $db->query(
        "SELECT * FROM customers WHERE id = ?",
        [$order['customer_id']]
    )->fetch();
    echo $order['ref'] . ' - ' . $customer['name'];
}

如果有 500 条订单,这段代码会触发 501 次查询:一次获取订单,然后每条订单再查一次客户。1,000 条订单时,就是 1,001 次查询。性能曲线会沿着最糟糕的方向线性增长。

修复方式:在循环外预加载关联数据。先取出所需的全部数据,再在循环内引用。

php
// 使用 JOIN 的单次查询
$orders = $db->query("
    SELECT orders.*, customers.name AS customer_name
    FROM orders
    JOIN customers ON customers.id = orders.customer_id
")->fetchAll();

foreach ($orders as $order) {
    echo $order['ref'] . ' - ' . $order['customer_name'];
}

或者,在确实需要保持查询分离时,例如出于缓存原因:

php
$orders      = $db->query("SELECT * FROM orders")->fetchAll();
$customerIds = array_column($orders, 'customer_id');

// 一次查询取出所有客户
$customers   = $db->query(
    "SELECT * FROM customers WHERE id IN (" . implode(',', $customerIds) . ")"
)->fetchAll();

// 按 ID 建索引,实现 O(1) 查找
$customersById = array_column($customers, null, 'id');

foreach ($orders as $order) {
    $customer = $customersById[$order['customer_id']];
    echo $order['ref'] . ' - ' . $customer['name'];
}

无论订单数量多少,总共两次查询。

为什么会出现:循环查询模式在快速开发时非常符合直觉。已经有一组订单,又需要客户姓名。自然会在循环里查客户,因为那时手里正好有 ID。性能影响只有在数据集增长后才会明显,而那时这种模式已经进入生产。

需要警惕:任何 foreachfor 循环里的数据库调用都值得调查。即使今天数据集很小,将来也可能增长。

异味 4:直接放弃的错误处理

设想正在排查一个生产 bug。日志显示确实出了问题,但所有有用的错误信息都被一个静默的 catch 块吞掉了。堆栈跟踪存在,却没有上下文。能知道某件事失败了,却不知道为什么失败。

php
// 异味:错误被静默吞掉
try {
    $result = $this->paymentGateway->charge($amount, $card);
} catch (Exception $e) {
    return false;
}

这个 catch 块没有做任何有意义的事。异常被捕获、被确认,然后被安静埋掉。调用者只得到 false,无法区分“银行卡被拒”、“网关超时”和“API key 无效”。

更危险的情况如下:

php
// 异味:更危险
function connectToDatabase(): PDO
{
    try {
        return new PDO($dsn, $user, $pass);
    } catch (PDOException $e) {
        die("Connection failed"); // 没有日志、没有上下文、没有恢复路径
    }
}

在库函数里调用 die(),相当于在库函数中直接触发了最激进的终止手段:没有错误日志,上层也没有处理机会。

修复方式:在合适的层级处理错误。记录带上下文的日志。调用者需要知道失败时,重新抛出异常。异常吞掉后,系统就失去了诊断能力。

php
try {
    $result = $this->paymentGateway->charge($amount, $card);
} catch (CardDeclinedException $e) {
    $this->logger->info('Card declined', ['card_last4' => $card->last4]);
    throw new PaymentFailedException('Your card was declined.', 0, $e);
} catch (GatewayTimeoutException $e) {
    $this->logger->error('Payment gateway timeout', ['amount' => $amount]);
    throw new PaymentFailedException('Payment service temporarily unavailable.', 0, $e);
} catch (Exception $e) {
    $this->logger->critical('Unexpected payment error', ['exception' => $e]);
    throw $e; // 重新抛出:意外错误需要暴露出来
}

具体的异常类型。有意义的日志消息。保留上下文。调用者收到的是可处理的异常,语义明确。

为什么会出现:try-catch 块通常一开始确实是错误处理,后来在“先让测试通过”或“阻止异常继续冒泡”的过程中被删减成空壳。空 catch 几乎总是表明失败模式的思考被推迟了。

需要警惕:任何既不记录日志、不重新抛出异常、也不采取有意义动作的 catch 块。空的 catch (Exception $e) {} 是 PHP 中最危险的模式之一,因为它会静默吞掉所有异常,包括那些本该提示严重问题的异常。

异味 5:到处都是魔法数字和魔法字符串

设想阅读一个账单函数时看到这样的代码:

php
if ($account->type === 3) {
    $discount = $subtotal * 0.15;
}

if ($order->status === 'pnd') {
    // 处理待处理订单
}

账户类型 3 是什么?'pnd' 表示什么?它是 pending 的缩写,还是拼写错误?0.15 是固定费率、可配置值,还是其他地方计算出来的结果?

这些就是魔法数字和魔法字符串:硬编码值缺少语义名称。写下它们的当天可以正常工作。当其他人接手时,它们就成了地雷。

php
// 修复方式:常量和枚举
class AccountType
{
    const ENTERPRISE = 3;
    const STANDARD   = 1;
    const TRIAL      = 2;
}

class OrderStatus
{
    const PENDING   = 'pnd';
    const CONFIRMED = 'cnf';
    const SHIPPED   = 'shp';
}

const ENTERPRISE_DISCOUNT_RATE = 0.15;

// 现在代码读起来像文档
if ($account->type === AccountType::ENTERPRISE) {
    $discount = $subtotal * ENTERPRISE_DISCOUNT_RATE;
}

if ($order->status === OrderStatus::PENDING) {
    // 处理待处理订单
}

更进一步,PHP 8.1 引入了原生枚举,在具名值之上提供类型安全:

php
enum AccountType: int
{
    case Standard   = 1;
    case Trial      = 2;
    case Enterprise = 3;
}

enum OrderStatus: string
{
    case Pending   = 'pnd';
    case Confirmed = 'cnf';
    case Shipped   = 'shp';
}

IDE 可以自动补全。类型检查器可以捕获非法值。任何阅读代码的开发者都能准确知道每个值的含义,不需要猜测,也不需要部落知识。

为什么会出现:写下值的时候,它看起来“显而易见”。开发者刚从数据库迁移文件过来,知道 3 表示企业账户。六个月后,所有人都不知道了,包括当时写下它的人。

需要警惕:数字状态码、缩写字符串值、硬编码百分比,以及在多个位置重复出现的字面量。一个值只要在代码库中出现多次,就应该拥有具名常量。

异味 6:改变一切的布尔参数

设想这个函数签名:

php
function getUsers(bool $includeDeleted, bool $sortByName, bool $activeOnly): array

再设想它的调用方式:

php
$users = getUsers(true, false, true);

true, false, true 分别是什么意思?必须回到函数签名,或依赖 IDE 提示才能记住。如果有人本想调用 getUsers(true, false, false),却写成 getUsers(false, true, false),这个 bug 在运行前几乎不可见。

这就是布尔参数异味,也常被称为 Boolean Trap。它会制造不可读的调用点,也会让函数根据标志位组合暗中变成两个、四个甚至八个不同函数。

php
// 这是获取活跃用户、按姓名排序、排除已删除用户?
// 还是获取活跃用户、不排序、包含已删除用户?
// 单看调用点无法判断。
$users = getUsers(false, true, false);

修复方式一:使用命名参数(PHP 8.0+)。

php
$users = getUsers(
    includeDeleted: false,
    sortByName: true,
    activeOnly: false
);

命名参数让调用点具备自解释能力。含义清晰,也降低对 IDE 的依赖。

修复方式二:拆成独立方法。

php
$users         = $userRepository->getActive();
$deletedUsers  = $userRepository->getIncludingDeleted();
$sortedUsers   = $userRepository->getActiveSortedByName();

每个方法都有清晰、无歧义的名称。每个方法只做一件事。布尔标志逻辑移动到方法实现内部,对调用者不可见。

修复方式三:查询对象或条件模式。

php
$criteria = UserCriteria::create()
    ->activeOnly()
    ->sortedByName()
    ->excludeDeleted();

$users = $userRepository->findByCriteria($criteria);

流畅、可读、可扩展。添加新过滤条件时无需改变函数签名。

为什么会出现:添加布尔参数时,它很像“灵活性”。已有一个函数几乎可用,只需为边界场景加一个标志。接着另一个边界场景又加一个标志。很快,这个函数就变成了伪装成简单调用的配置对象。

需要警惕:拥有多个布尔参数的函数几乎总是异味。单个布尔参数是黄色警示。两个及以上就是红色警示。

异味 7:解释“做了什么”的注释,缺少“为什么”

这个问题更隐蔽,也是很多开发者最容易反过来处理的问题。

设想这段代码:

php
// Loop through users
foreach ($users as $user) {
    // Check if user is active
    if ($user->status === 'active') {
        // Send email
        $this->mailer->send($user->email, $template);
    }
}

每条注释都在解释代码做了什么。但代码本身已经展示了它在做什么;这正是代码的职责。这些注释只增加视觉噪音,并没有增加知识。更糟的是,它们会产生维护负担:代码变化后,注释经常没有同步更新,最终变成会误导读者的注释。

真正的异味在于注释了错误的内容。好注释解释原因,传递代码本身难以表达的上下文。

php
// 修复方式:注释承载代码本身无法表达的信息
foreach ($users as $user) {
    // 只有活跃用户接收通知;根据 2023 合规审计(JIRA-1847),
    // 暂停账户和试用账户需要排除。
    if ($user->status === 'active') {
        $this->mailer->send($user->email, $template);
    }
}

现在,这条注释值得存在。它说明检查存在的原因,引用了特定决策,并为未来开发者提供了仅靠阅读代码无法获得的上下文。

最好的代码完全不需要“做了什么”类型的注释,因为代码本身已经足够可读:

php
foreach ($activeUsers as $user) {
    $this->mailer->sendNotification($user);
}

变量名和方法名承载“做了什么”。注释承载“为什么”。当二者各自完成职责时,代码会形成清晰、可追踪的执行逻辑。

为什么会出现:开发者在职业早期常被教导“给代码写注释”,却很少被教导区分有用注释和无用注释。结果就是代码里堆满了复述显而易见内容的注释,增加噪音,却没有增加信号。

需要警惕:以 “Loop through...”、“Check if...”、“Get the...” 或 “Set the...” 开头的注释,几乎总是在复述代码。删除它们后观察是否丢失信息。多数情况下不会,代码还会更干净。

贯穿这七种异味的共同线索

回看这份清单。God Function。无意义变量名。N+1 查询。被吞掉的异常。魔法数字。布尔陷阱。冗余注释。

它们有什么共同点?

每一个都是当下看起来合理的决策。它们诞生于压力、截止日期,以及对代码库未来增长方式的信息不足。它们体现了开发者在压力下为了让功能立即可用而做出的局部理性选择。

问题在于,代码会活得比制造它的截止日期更久。周四凌晨两点走的捷径,会变成别人周五早上的问题,也会变成下周一和第二季度的问题。

代码异味属于会复利的选择。一个魔法数字只是轻微不便。满是魔法数字的代码库,会成为没人敢自信修改的系统。

在压力下仍能写出整洁代码的开发者,通常知道哪些捷径安全,哪些捷径迟早会回来索债;当他们不得不走危险捷径时,会留下说明。

需要避免的陷阱

在没有测试的情况下重构异味。如果正在清理一个没有单元测试的 God Function,先写测试。缺少安全网的重构,很容易让“修复异味”变成“引入回归”。

孤立地重命名变量。如果只在一个函数中把 $data 改成 $orderLines,却让同一文件里的其他 $data 原样保留,得到的是不一致。重命名应保持一致,暂缓处理也比制造混乱更稳妥。

过度设计修复方案。God Function 的解法是职责清晰的小函数,避免接口、适配器、工厂类层层叠加的十二层抽象。修复强度应匹配问题规模。

把注释当作可读代码的替代品。如果需要注释解释变量名含义,就应该重命名变量。用注释补偿糟糕命名,是异味叠加异味。

在代码审查中只修实例,不提升为模式讨论。如果在 PR 中发现一个魔法数字,可以修掉这个实例;同时也要向团队指出这个模式。单次修复能消除一个发生点。团队讨论能阻止模式继续扩散。

迷你问答

问:当团队觉得“它能跑”时,如何说服团队重视代码异味?
把它描述为维护成本。“这个函数有 300 行,很难安全修改”比“这段代码很乱”更容易产生共识。具体风险更有说服力,例如“如果要加这个功能,就必须修改一段尚未完全理解的代码”。

问:发现异味时应该顺手修,还是安排专门的重构时间?
两者都有价值。童子军规则,也就是“让代码比发现它时更干净”,适合作为增量改进的默认策略。对于 God Function 这类需要更大变更和协调测试的结构性问题,专门重构会更合适。

问:N+1 查询检测是否属于 ORM 的职责?
现代 ORM(Eloquent、Doctrine)很容易引入 N+1,也提供了检测工具,例如 Laravel Debugbar 和 Doctrine SQL logging。但 ORM 只提供信号和工具。理解这个模式,才是可靠预防的基础。

问:在现有代码库中查找代码异味的最佳方式是什么?
静态分析工具,例如 PHPStan、Psalm、PHP CodeSniffer,可以自动捕获许多异味。对于 God Function 这类架构异味,先用 wc -l 查文件长度或函数长度是一个快速入口。最可靠的方法仍然是代码审查:另一双眼睛能看到自动化工具漏掉的问题。

为什么它超越了代码质量本身

设想一次高级岗位面试中,招聘经理要求候选人审查一个 PHP 文件。候选人打开文件,立刻发现本文 7 种异味中的 3 种。

如何表达所看到的问题,决定了回答的层级。只说“这很糟”还不够;能说明它为什么有问题、带来什么风险、具体应该如何修复,才是高级开发者的回答。

代码异味意识是一种信号。它说明开发者的关注点已经从“能否运行”扩展到“能否维护”。这正是写代码的开发者与拥有系统的开发者之间的差异。

本文 7 个模式并未穷尽所有异味。但它们最常见、影响最大,也最容易修复。认识它们会改变读代码的方式。应用修复方式会改变写代码的方式。

收束:异味是工程线索

代码异味是压力下的开发者做出局部理性决策,并最终带来全局非理性后果的证据。审视代码异味的目标是识别维护风险和形成原因,并为后续改进提供方向。

目标是识别模式、理解它们形成的原因,并用更稳固的东西替代它们。代码要在今天站得住,也要在两年后有人打开文件并修改时保持可靠。

这份清单里的每一次重构都可以完成。它们都不要求推翻已有代码。多数重构所需时间,还少于当初写出原始代码的时间。

写出下一位开发者可以信任的代码。有时,那位开发者就是六个月后的自己:周一早上九点,生产事故已经打开,而自己完全记不起这个函数原本应该做什么。

7 天小计划

第 1 天:找出当前项目中最长的函数。如果超过 50 行,识别其中不同职责。

第 2 天:在代码库中搜索 $data$result$temp$arr。统计命中次数。重命名其中三个。

第 3 天:搜索循环中的数据库查询。找出一个,并重构为 JOIN 或批量获取。

第 4 天:找出一个空的或近乎空的 catch 块。添加日志和有意义的异常。

第 5 天:搜索数字字面量和短字符串编码,例如 12'pnd''act'。抽取三个为具名常量或枚举。

第 6 天:找出一个有两个或更多布尔参数的函数。用命名参数或拆分方法重构它。

第 7 天:删除代码库中的五条“做了什么”注释。在上下文真正重要的位置添加一条“为什么”注释。

关键指标:代码库中的平均函数长度。一般目标是每个函数少于 30 行。异常值通常就是 God Function 藏身之处。

常见错误:重构异味后没有运行测试套件。采用小步、安全、经过测试的修改,避免一次性提交会破坏六处地方的巨大清理。

行动号召

这 7 种异味中,哪一种在经手的代码库中最常见?或者更坦诚一点,哪一种最容易在日常开发中被写出来?可以在评论里写出来。这里没有审判,只有开发者之间对那些最终反噬自己的捷径进行经验交换。

如果这篇文章为团队中已经存在却缺少准确词汇的讨论提供了语言,就把它分享出去。当每个人都用同一张地图阅读代码时,代码审查会变得更好。

闭环

设想六个月后打开一个文件,里面的代码立刻清晰、自然、显而易见。无需侦探工作。无需考古。只有能够自我说明的逻辑。

这是可以达成的结果:团队识别这些模式、命名这些模式,并决定截止日期不值得用债务来交换。

异味可以修复。习惯可以学习。先从一个开始。

读者常问的 8 个问题

1. PHP 中的代码异味是什么?
代码异味是代码中提示更深层问题的模式。代码仍可运行,但某些设计或风格选择会让它更难阅读、测试或维护。常见 PHP 异味包括 God Function、魔法数字、N+1 查询、被吞掉的异常和布尔参数陷阱。

2. 如何在 PHP 项目中检测代码异味?
PHPStan、Psalm 和 PHP CodeSniffer 等静态分析工具可以自动捕获许多异味。对于架构异味,人工代码审查最有效:检查长函数(wc -l)、通用变量名(搜索 $data$result),以及循环中的数据库调用。

3. PHP 中的 N+1 查询问题是什么?
N+1 问题指代码先运行一次查询获取列表,然后对列表中的每一项再单独运行一次查询,最终产生 N+1 次查询。它是使用 ORM 或手写查询循环的 PHP 应用中常见的性能杀手。修复方式是预加载:在循环前用一到两次查询取出全部关联数据。

4. PHP 中的 God Function 是什么?
God Function 是承担过多职责的函数,例如校验、数据库访问、业务逻辑、邮件发送、日志记录和响应格式化都放在同一个地方。它违反单一职责原则,也几乎无法单元测试或安全修改。

5. 为什么布尔参数是代码异味?
布尔参数会让函数调用点不可读,例如 getUsers(true, false, true) 离开函数签名后无法传达含义。它还说明一个函数可能根据标志位组合表现为多个不同函数。修复方式包括命名参数(PHP 8.0+)、拆分方法,或查询/条件对象模式。

6. PHP 注释应该解释什么?
注释应该解释代码为什么这样做。代码本身已经展示了它做了什么;复述显而易见内容的注释只会增加噪音。有用注释会解释业务规则、引用决策票据、提醒非显而易见的副作用,或记录为何主动放弃一个更简单的方案。

7. PHP 枚举如何帮助解决魔法数字?
PHP 8.1 原生枚举可以用具名且类型安全的 case 替代硬编码字面量。相比 $status === 3 这种缺少语义的写法,$status === OrderStatus::Confirmed 具备自解释能力。枚举还支持 IDE 自动补全和静态分析检查,这是原始整数和字符串难以提供的。

8. 哪些工具可以自动捕获 PHP 代码异味?
PHPStan 和 Psalm 执行静态分析,能捕获类型错误、未定义变量和结构问题。PHP CodeSniffer 强制编码标准。PHP Mess Detector(PHPMD)专门针对长函数、参数过多和圈复杂度等异味。Laravel Debugbar 可以在开发环境中捕获运行时 N+1 查询。

注:“凌晨两点截止日期”这一叙事框架用于说明这些模式形成的真实环境。发布时,可以考虑链接到 PHP 社区中的具体事故复盘或案例研究,让叙事拥有可引用的真实事件基础。

本作品采用《CC 协议》,转载必须注明作者和本文链接