错误处理 #

一、基本错误处理 #

1.1 die函数 #

die 抛出致命错误并终止程序:

perl
my $file = "nonexistent.txt";
open my $fh, "<", $file or die "Cannot open $file: $!";

1.2 warn函数 #

warn 输出警告但不终止程序:

perl
my $file = "nonexistent.txt";
open my $fh, "<", $file or warn "Cannot open $file: $!";

1.3 $!变量 #

$! 包含系统错误信息:

perl
open my $fh, "<", "nonexistent.txt"
    or die "Error: $!";

1.4 Carp模块 #

Carp 提供更好的错误报告:

perl
use Carp qw(croak confess cluck);

sub process {
    my $file = shift;
    open my $fh, "<", $file or croak "Cannot open $file: $!";
}

sub debug_process {
    my $file = shift;
    open my $fh, "<", $file or confess "Cannot open $file: $!";
}
函数 说明
croak die,报告调用位置
confess die,报告完整调用栈
cluck warn,报告调用位置
carp warn,报告调用位置

二、eval块 #

2.1 基本用法 #

eval 捕获运行时错误:

perl
my $result = eval {
    my $a = 10;
    my $b = 0;
    $a / $b;
};

if ($@) {
    print "Error: $@\n";
} else {
    print "Result: $result\n";
}

2.2 eval字符串 #

eval 也可以执行字符串代码(不推荐):

perl
my $code = 'print "Hello\n"';
eval $code;

if ($@) {
    print "Error: $@\n";
}

2.3 安全使用eval #

perl
sub safe_divide {
    my ($a, $b) = @_;
    
    my $result = eval { $a / $b };
    
    if ($@) {
        warn "Division error: $@";
        return undef;
    }
    
    return $result;
}

2.4 eval块最佳实践 #

perl
my $result;

eval {
    $result = some_operation();
    1;
} or do {
    my $error = $@;
    handle_error($error);
};

三、Try::Tiny模块 #

3.1 基本用法 #

Try::Tiny 提供更清晰的异常处理:

perl
use Try::Tiny;

try {
    my $result = 10 / 0;
} catch {
    warn "Error: $_";
} finally {
    print "Cleanup\n";
};

3.2 返回值 #

perl
use Try::Tiny;

my $result = try {
    return some_operation();
} catch {
    warn "Error: $_";
    return undef;
};

3.3 完整示例 #

perl
use Try::Tiny;

sub process_file {
    my $file = shift;
    
    my $content = try {
        open my $fh, "<", $file or die "Cannot open: $!";
        local $/;
        my $data = <$fh>;
        close $fh;
        return $data;
    } catch {
        warn "Failed to read $file: $_";
        return undef;
    };
    
    return $content;
}

四、自定义异常 #

4.1 使用die抛出异常 #

perl
sub withdraw {
    my ($balance, $amount) = @_;
    
    if ($amount > $balance) {
        die "Insufficient funds: balance=$balance, requested=$amount";
    }
    
    return $balance - $amount;
}

eval {
    withdraw(100, 150);
};
if ($@) {
    print "Transaction failed: $@\n";
}

4.2 异常对象 #

perl
package MyException;
use overload '""' => \&stringify;

sub new {
    my ($class, %args) = @_;
    return bless \%args, $class;
}

sub message { shift->{message} }
sub code    { shift->{code} }

sub stringify {
    my $self = shift;
    return "Error [$self->{code}]: $self->{message}";
}

1;

使用:

perl
use Try::Tiny;

try {
    die MyException->new(
        message => "Something went wrong",
        code    => 500,
    );
} catch {
    if (ref $_ eq 'MyException') {
        print "Custom error: $_\n";
    } else {
        print "Unknown error: $_\n";
    }
};

五、错误处理模式 #

5.1 返回错误码 #

perl
sub divide {
    my ($a, $b) = @_;
    
    return (undef, "Division by zero") if $b == 0;
    return ($a / $b, undef);
}

my ($result, $error) = divide(10, 0);
if ($error) {
    print "Error: $error\n";
} else {
    print "Result: $result\n";
}

5.2 使用undef表示错误 #

perl
sub read_config {
    my $file = shift;
    
    open my $fh, "<", $file or return;
    local $/;
    my $content = <$fh>;
    close $fh;
    
    return $content;
}

my $config = read_config("config.txt");
unless (defined $config) {
    die "Failed to read config";
}

5.3 回调错误处理 #

perl
sub process {
    my (%args) = @_;
    
    my $on_success = $args{on_success} // sub {};
    my $on_error   = $args{on_error}   // sub { die @_ };
    
    my $result = eval { do_something() };
    
    if ($@) {
        $on_error->($@);
    } else {
        $on_success->($result);
    }
}

process(
    on_success => sub { print "Success: $_[0]\n" },
    on_error   => sub { warn "Error: $_[0]" },
);

六、日志记录 #

6.1 简单日志 #

perl
sub log_error {
    my $msg = shift;
    my $timestamp = localtime();
    print STDERR "[$timestamp] ERROR: $msg\n";
}

sub log_info {
    my $msg = shift;
    my $timestamp = localtime();
    print STDERR "[$timestamp] INFO: $msg\n";
}

eval {
    log_info("Starting operation");
    some_operation();
    log_info("Operation completed");
};
if ($@) {
    log_error($@);
}

6.2 使用Log::Log4perl #

perl
use Log::Log4perl;

Log::Log4perl->init(\<<'END');
log4perl.rootLogger=DEBUG, Screen

log4perl.appender.Screen=Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout=PatternLayout
log4perl.appender.Screen.layout.ConversionPattern=%d %p %m %n
END

my $logger = Log::Log4perl->get_logger();

$logger->debug("Debug message");
$logger->info("Info message");
$logger->warn("Warning message");
$logger->error("Error message");
$logger->fatal("Fatal message");

七、实践练习 #

练习1:安全文件操作 #

perl
#!/usr/bin/perl
use strict;
use warnings;
use Try::Tiny;
use v5.10;

sub safe_read_file {
    my $file = shift;
    
    my $content = try {
        open my $fh, "<", $file or die "Cannot open: $!";
        local $/;
        my $data = <$fh>;
        close $fh;
        return $data;
    } catch {
        warn "Error reading $file: $_";
        return undef;
    };
    
    return $content;
}

my $content = safe_read_file("test.txt");
if (defined $content) {
    say "Read " . length($content) . " bytes";
}

练习2:数据库操作 #

perl
#!/usr/bin/perl
use strict;
use warnings;
use Try::Tiny;
use v5.10;

sub db_query {
    my ($sql, @params) = @_;
    
    my $result = try {
        die "Connection failed" unless connect_db();
        my $sth = prepare($sql);
        $sth->execute(@params);
        return $sth->fetchall_arrayref();
    } catch {
        log_error($_);
        return undef;
    };
    
    return $result;
}

sub log_error {
    my $msg = shift;
    my $time = localtime();
    print STDERR "[$time] ERROR: $msg\n";
}

sub connect_db { return 1; }
sub prepare { return bless {}, 'STH'; }

package STH;
sub execute {}
sub fetchall_arrayref { return []; }

练习3:配置验证 #

perl
#!/usr/bin/perl
use strict;
use warnings;
use Try::Tiny;
use v5.10;

sub validate_config {
    my $config = shift;
    my @errors;
    
    try {
        die "Missing 'host'" unless $config->{host};
        die "Invalid 'port'" unless $config->{port} && $config->{port} > 0;
        die "Missing 'database'" unless $config->{database};
    } catch {
        push @errors, $_;
    };
    
    return @errors ? \@errors : undef;
}

my $config = {
    host => "localhost",
    port => 3306,
    database => "mydb",
};

my $errors = validate_config($config);
if ($errors) {
    say "Validation errors:";
    say "  $_" foreach @$errors;
} else {
    say "Config is valid";
}

八、总结 #

本章学习了:

  • die和warn函数
  • eval块捕获错误
  • Try::Tiny模块
  • 自定义异常
  • 错误处理模式
  • 日志记录

下一章将学习CPAN模块管理。

最后更新:2026-03-27