7.1 错误处理


7.1 错误处理

die 立即退出、warn 记录继续、eval 捕获后自定——Perl 错误处理就这三档。工程要点不在语法,而在给每个错误源预先分级:跳过、重试还是终止。

三档机制

# 一档:warn,记录后继续(可忽略异常) warn "行 $. 无法解析,已跳过\n" unless $rec; # 二档:die,带原因退出(致命错误) open my $fh, '<', $src or die "无法读取 $src: $!"; # 三档:eval 块捕获 die,转为自己处理(可恢复异常) my $ok = eval { process_file($f); # 内部任何 die 都被拦下 1; }; if (!$ok) { warn "跳过 $f: $@"; # $@ 里是 die 的消息 $failed_files++; }

块版 eval { ... } 只捕获运行期错误,语法错误在编译期就拦下——这是它和老字符串 eval 的本质区别,新代码只用块版。

给流水线配分级策略

第 5 章的合并流水线配上完整的分级:

die "目录不存在: $dir" unless -d $dir; # 前置校验:直接停 my @files = glob "$dir/*.log"; die "没有可处理文件" unless @files; # 前置校验:直接停 for my $f (@files) { my $ok = eval { process_file($f); 1 }; warn "$f 处理失败: $@" and next unless $ok; # 单文件失败:跳过 }

原则一句话:开始之前的问题(配置、路径)用 die 早停;跑到中途的问题(单条记录、单个文件)用 warn 或 eval 扛过去。早停省时间,扛住保全局。

自定义 die 与错误对象

open my $fh, '<', $src or die { file => $src, reason => $!, at => localtime() };

die 一个引用,$@ 拿到的就是结构化数据,外层可以按字段分流处理。简单脚本用字符串足够,模块接口建议用引用,调用方解析不用猜格式。

⚠️ 常见坑:eval 之后不查 $@ 也不查返回值,等于白包一层。local 化 $@ 或改用返回值判断(my $ok = eval { ...; 1 })更稳——$@ 在个别边缘情况下会被后续代码清空。

Try::Tiny 与错误分层:把三档写成可读的流程

裸 eval 的返回值判断够用但略隐晦,模块化代码更常用 Try::Tiny,语义与主流语言的 try/catch 对齐:

use Try::Tiny; try { process_file($f); } catch { my $err = $_; # 注意是 $_,不是 $@ warn "跳过 $f: $err"; $failed++; } finally { $done++; # 无论成败都执行,适合计数收尾 };

用它的收益不在功能而在可读性:catch 里拿 $_、finally 兜收尾,三段职责一眼分清,也顺便绕开了 $@ 被清空的经典坑。选型建议与 6.4 节一致——单脚本裸 eval 足矣,进模块、要被别人调用时上结构化方案。

分级策略速查表

把"跳过、重试还是终止"的决策压成一张表,遇到新错误源时对号入座:

错误源 级别 动作 理由
配置错、目录不存在 die 立即停 后面全白跑
单文件打不开 eval+跳过 记数继续 别拖累整批
单行解析失败 warn 计数继续 数据质量问题
网络类间歇失败 try+重试 退避后重试 多为瞬态
写报告失败 die 立即停 产出不可信

第五行值得强调:读端可以宽容,写端必须严格——统计再多,报告写不出来或写坏了就等于零。所以写报告的 eval 捕获后应当 die 而不是跳过,方向别配反。

信号处理:让长跑任务体面收场

跑数小时的批处理被 Ctrl-C 或 kill 打断时,默认行为是立刻死掉,留下临时文件与未提交状态。%SIG 哈希让脚本在信号到来时先执行清理:

my $tmp = 'report.txt.tmp'; $SIG{INT} = $SIG{TERM} = sub { warn "收到中断,清理 $tmp 后退出\n"; unlink $tmp; $dbh->rollback if $dbh; # 回滚未提交事务 exit 1; }; # ... 长跑主循环 ... END { unlink $tmp if $interrupted } # 双保险:正常/异常退出都尝试清理

两个实践要点:信号处理器里只做"快而确定"的清理(删文件、回滚),别做 IO 密集活——信号可能在不合时宜的中间态到达;要彻底忽略误触信号用 IGNORE(如 $SIG{PIPE} = 'IGNORE' 防管道断裂连锁)。长跑任务的"体面"就体现在被中断时现场干净:不留临时文件、不留半批数据、日志里有交代——这三个承诺比多跑十分钟重要得多。信号与 eval 的分工也顺带理清:eval 管的是"可预期的失败",信号管的是"外部的打断",两套机制覆盖的是错误的两个来向,配起来才是完整的防线。给它们排个出场顺序也有讲究:信号处理器在最外层兜底(它随时可能到),eval 在中层围住可疑段,warn/die 在最里层逐点响应——三层同心圆,从外到内依次响应"打断、失败、瑕疵",画在纸上就是本章的完形。落地检查也照这个层次走:新脚本先问"会被 kill 吗"(配信号清理),再问"哪段可能失败"(配 eval),最后逐点配 warn/die——三层齐了,错误处理的评审就可以画勾。也把反例记一笔:三层堆到极端就是过度防御——每个 print 都包 eval、每步都重试三次的脚本,真正的问题反而被噪音淹没。分级的艺术在于"够用即止":按分级表配档位,表外的保持简单,这样的防线才既挡得住风雨、又看得见路。

本节要点回顾

  • warn 继续、die 终止、eval 捕获自定,三档覆盖所有场景
  • 块 eval 只管运行期,语法错误编译期就报
  • 前置校验用 die 早停,中途故障用 eval 单点跳过
  • die 可以抛引用,错误信息结构化
  • 判断 eval 成败优先看返回值eval { ...; 1 } 是惯用法

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