1.9 错误处理与调试:站台广播与事故登记簿


文档摘要

1.9 错误处理与调试:站台广播与事故登记簿 本节摘要:代码出错不可怕,可怕的是出错后旅客看见白屏、日志里却一片安静。本节把错误处理讲成两件事:广播策略(开发期大声报、生产期登记不示人)与兜底预案(异常捕获、日志落盘、友好退路)。读完后你应当会配置两套环境各自的错误策略,能读懂一条异常堆栈,并掌握一套按段排查的调试流程——本章所有环节出错,最终都汇到这一节处理。 一条报错信息能读出什么 先练读。下面这条是 PHP 最典型的告警,逐段拆开: 四个信息位:等级 (告警,脚本继续跑);内容 (访问了不存在的键——1.4 节的坑);位置前半段是文件路径;行号 12。多数报错按"等级、内容、位置"三步读,定位基本不超一分钟。真正麻烦的是等级为 的致命错:脚本当场收班,页面只吐半截。

1.9 错误处理与调试:站台广播与事故登记簿

本节摘要:代码出错不可怕,可怕的是出错后旅客看见白屏、日志里却一片安静。本节把错误处理讲成两件事:广播策略(开发期大声报、生产期登记不示人)与兜底预案(异常捕获、日志落盘、友好退路)。读完后你应当会配置两套环境各自的错误策略,能读懂一条异常堆栈,并掌握一套按段排查的调试流程——本章所有环节出错,最终都汇到这一节处理。

一条报错信息能读出什么

先练读。下面这条是 PHP 最典型的告警,逐段拆开:

Warning: Undefined array key "page" in /site/public/search.php on line 12

四个信息位:等级 Warning(告警,脚本继续跑);内容 Undefined array key(访问了不存在的键——1.4 节的坑);位置前半段是文件路径;行号 12。多数报错按"等级、内容、位置"三步读,定位基本不超一分钟。真正麻烦的是等级为 Fatal error 的致命错:脚本当场收班,页面只吐半截。所以策略层面要做的第一件事,是让所有等级的错误都被看见(开发期)或被登记(生产期),而不是漏掉。

<?php // 开发期配置:全等级报告,直接显示,不落日志也行 error_reporting(E_ALL); ini_set('display_errors', '1'); // 生产期配置:登记不示人,给旅客看友好退路 ini_set('display_errors', '0'); // 绝不向旅客广播技术细节 ini_set('log_errors', '1'); // 转入事故登记簿 ini_set('error_log', __DIR__ . '/logs/php_error.log');

两套配置的差异只有一句原则:开发期错误是提示牌,生产期错误是内部台账。把 display_errors 开到生产上,等于把仓库内布局图贴给所有旅客看,路径、参数一览无余,安全隐患直接翻倍。

异常机制:把事故变成可接手的交接

PHP 把运行期问题分成两类:错误(Error,多为语法或环境问题,难以在业务层恢复)与异常(Exception,业务逻辑抛出、应当被接手处理)。两者都实现 Throwable 接口,所以捕获时一把抓:

<?php function fare(int $km): float { if ($km <= 0) { throw new InvalidArgumentException('里程必须为正数'); } return $km * 0.8; } try { echo fare(-5), "\n"; } catch (InvalidArgumentException $e) { echo '参数问题:', $e->getMessage(), "\n"; // 参数问题:里程必须为正数 } catch (Throwable $e) { echo '其他意外:', $e->getMessage(), "\n"; } finally { echo '清理动作照常执行\n'; // 无论是否抛出都执行 }

读法要点:try 圈出可能出事的段落;catch 从具体到一般排列,让参数问题得到专门处置、其他意外有兜底;finally 收尾清理(关句柄、释放锁),无论成败都执行。$e->getMessage() 是人话描述,$e->getTraceAsString() 是完整堆栈——生产事故登记时后者必不可少。

异常更重要的用法是"自定义异常做业务信号"。给不同业务失败起不同类名,上层就能按类精准接手:

<?php class SeatSoldOut extends RuntimeException {} function bookSeat(string $no): string { $soldOut = ['12F']; if (in_array($no, $soldOut, true)) { throw new SeatSoldOut("座位 {$no} 已售出"); } return "已锁定 {$no}"; } try { echo bookSeat('12F'), "\n"; } catch (SeatSoldOut $e) { echo '请换座:', $e->getMessage(), "\n"; }

判断标准一句话:能预期、该给旅客台阶下的,抛业务异常;意料之外的环境故障,让它向上炸,由全局兜底统一登记。

调试流程:按段排除,别靠猜

出错时按固定顺序排查,比盯着代码干瞪眼快得多。一套实战流程:

<?php // 第一段:确认输入符合预期——一半以上的"灵异 bug"是输入与想象不符 var_dump($_POST); // 第二段:确认中间状态——在关键交接点打印 $clean = array_filter($raw, fn($v) => $v !== ''); var_dump(count($clean)); // 第三段:确认边界与出口——分支是否走对、返回值是否如预期 var_dump($errors);

配合两个纪律:其一,var_dump 打完即删或换成开关控制的日志,绝不能留在生产代码里;其二,排除法沿生命周期图(1.1 节)走——请求没进站查服务器配置,进站没执行查脚本路径,执行了结果不对查业务逻辑,一段一段锁区间。日志是最好的搭档:

<?php // 结构化登记:时间、等级、上下文一行齐,方便按天检索 function logError(string $where, string $msg): void { $line = sprintf("%s [%s] %s: %s\n", date('Y-m-d H:i:s'), 'ERROR', $where, $msg); file_put_contents(__DIR__ . '/logs/app.log', $line, FILE_APPEND | LOCK_EX); } try { bookSeat('12F'); } catch (Throwable $e) { logError('bookSeat', $e->getMessage() . ' @ ' . get_class($e)); http_response_code(500); echo '月台临时故障,请稍后再试'; // 给旅客的友好退路 }

这套"登记 + 退路"的组合是 1.6 节仓库能力与异常机制的结合,也是全册后续所有"失败场景"的标准收尾姿势。第 2.8 节会把"预期中的失败"进一步固化成自动化测试用例,让问题在发车前就暴露。

常见坑

⚠️ 用 @ 压制报错:错误没消失,只是被捂住了嘴,排查时全线失聪。要么修,要么显式接手。

⚠️ catch 之后吞掉异常:空 catch 块让事故无声蒸发,至少登记一行日志再决定要不要继续抛。

本节要点回顾

  • 两套广播策略:开发期全等级显示,生产期只登记不示人,display_errors 在线上必须关。
  • 异常按类接手:从具体异常到 Throwable 兜底排列,finally 负责清理。
  • 业务失败即自定义异常:预期中的失败用异常类名表达,上层按类精准处置。
  • 调试按段排除:输入、中间状态、边界出口依次验证,日志一行一上下文。
  • 登记加固退路:出错必留痕,旅客给台阶,这是贯穿全册的失败处理姿势。

下一章进入站内调度:语言工具已经备齐,接下来是把数据库、性能、安全这些真正的服务端主题逐个搬上月台。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U