Throwable::Error 是 Perl 里一个流行的异常基类模块,和 Try::Tinyeval 结合使用,可以让你的代码拥有类似现代语言的异常处理体验。你可以很方便地自定义异常类型,并在代码中用 throw 抛出,然后用 try/catch 捕获。

下面详细介绍 Throwable::Error 的用法,给出几个典型示例:


1. 基本自定义异常

定义异常类:

package MyApp::Exception;
use parent 'Throwable::Error';  # 继承Throwable::Error
1;

抛出异常:

use MyApp::Exception;

sub may_fail {
    my $ok = rand() > 0.5 ? 1 : 0;
    MyApp::Exception->throw("失败啦") unless $ok;
    return "成功";
}

捕获异常(推荐 Try::Tiny):

use Try::Tiny;

try {
    my $result = may_fail();
    print "结果: $result\n";
} catch {
    warn "捕获到异常: $_";  # $_ 是异常对象
};


2. 异常对象含属性

你可以给异常对象加自定义属性(比如 code、details):

package MyApp::Exception;
use parent 'Throwable::Error';

sub code { shift->{code} }
sub details { shift->{details} }

1;

抛出异常带属性:

MyApp::Exception->throw(
    message => "请求失败",
    code    => 500,
    details => { foo => 'bar' },
);

捕获并读取属性:

try {
    ...
} catch {
    if (eval { $_->isa('MyApp::Exception') }) {
        warn "异常信息: " . $_->message;
        warn "异常代码: " . $_->code;
        warn "详细: " . Dumper($_->details);
    } else {
        warn "未知异常: $_";
    }
};


3. 多种异常类型

你可以定义多个异常类,继承自同一个基类:

package MyApp::Exception::Network;
use parent 'Throwable::Error';
1;

package MyApp::Exception::Input;
use parent 'Throwable::Error';
1;

使用和捕获不同类型异常:

try {
    if ($something_wrong) {
        MyApp::Exception::Network->throw("网络出错");
    } elsif ($bad_input) {
        MyApp::Exception::Input->throw("输入不合法");
    }
} catch {
    if (eval { $_->isa('MyApp::Exception::Network') }) {
        warn "网络相关异常: $_";
    } elsif (eval { $_->isa('MyApp::Exception::Input') }) {
        warn "输入相关异常: $_";
    } else {
        warn "其他异常: $_";
    }
};


4. 结合 Cloudflare::DNS 的真实例子

异常类:

package Cloudflare::DNS::Exception;
use parent 'Throwable::Error';

sub error_code { shift->{error_code} }
sub cf_errors  { shift->{cf_errors} }

1;

在模块内部抛出异常:

use Cloudflare::DNS::Exception;

sub create_record {
    my ($self, %args) = @_;
    my $res = $self->_do_api_request(...);

    unless ($res->{success}) {
        Cloudflare::DNS::Exception->throw(
            message    => "Cloudflare API 错误",
            error_code => $res->{code},
            cf_errors  => $res->{errors},
        );
    }
    return $res->{result};
}

调用方处理:

use Try::Tiny;

try {
    $dns->create_record(...);
} catch {
    if (eval { $_->isa('Cloudflare::DNS::Exception') }) {
        warn "CF API异常: " . $_->message;
        warn "CF错误码: " . $_->error_code;
        warn "CF错误详情: " . Dumper($_->cf_errors);
    } else {
        warn "未知错误: $_";
    }
};


5. 兼容 eval 方式

Throwable::Error 也支持老的 eval 风格:

eval {
    $dns->create_record(...);
};
if ($@) {
    if (eval { $@->isa('Cloudflare::DNS::Exception') }) {
        warn "Cloudflare异常: " . $@->message;
    } else {
        warn "其它异常: $@";
    }
}

不过一般推荐 Try::Tiny,更安全。


总结