Skip to content

Perl 错误处理

die 和 warn

die - 致命错误

perl
# 基本使用
open(my $fh, '<', 'nonexistent.txt')
    or die "Cannot open file: $!";

# 带行号
die "Error occurred at line " . __LINE__ . "\n";

# 包含文件名
die "Error in file " . __FILE__ . " at line " . __LINE__ . "\n";

warn - 警告

perl
# 基本使用
warn "This is a warning message\n";

# 带条件
warn "Value is negative: $value\n" if $value < 0;

# 在警告中包含更多信息
warn "Warning at " . __FILE__ . " line " . __LINE__ . "\n";

$! - 系统错误消息

perl
open(my $fh, '<', 'file.txt')
    or die "Cannot open file: $!";

# $! 包含系统错误描述
# 例如:"No such file or directory"

$? - 子进程退出状态

perl
system("ls -l");

if ($? != 0) {
    die "Command failed with status: $?";
}

eval - 错误捕获

基本 eval

perl
# 捕获致命错误
eval {
    open(my $fh, '<', 'nonexistent.txt')
        or die "Cannot open file";
};

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

返回值

perl
my $result = eval {
    # 可能出错的代码
    10 / 0;
};

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

嵌套 eval

perl
eval {
    eval {
        die "Inner error";
    };
    
    if ($@) {
        warn "Caught inner error: $@";
        die "Outer error";
    }
};

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

自定义错误处理

使用信号处理器

perl
# 处理 INT 信号(Ctrl+C)
$SIG{INT} = sub {
    print "\nInterrupted!\n";
    exit;
};

# 处理 TERM 信号
$SIG{TERM} = sub {
    print "Terminated!\n";
    cleanup();
    exit;
};

sub cleanup {
    print "Cleaning up...\n";
}

使用 DIE 处理器

perl
local $SIG{__DIE__} = sub {
    my ($error) = @_;
    print "Custom error handler: $error";
    # 可以在这里记录日志、发送邮件等
};

die "Something went wrong\n";

使用 WARN 处理器

perl
local $SIG{__WARN__} = sub {
    my ($warning) = @_;
    print "Custom warning handler: $warning";
};

warn "This is a warning\n";

Try::Tiny 模块

基本 try/catch

perl
use Try::Tiny;

try {
    # 可能出错的代码
    open(my $fh, '<', 'file.txt') or die "Cannot open file";
}
catch {
    my $error = shift;
    print "Caught error: $error\n";
};

try/catch/finally

perl
use Try::Tiny;

try {
    # 主要代码
    open(my $fh, '<', 'file.txt') or die "Cannot open file";
    # 处理文件...
}
catch {
    my $error = shift;
    print "Error: $error\n";
}
finally {
    # 总是执行的代码
    print "Cleanup\n";
};

返回值

perl
use Try::Tiny;

my $result = try {
    return 42;
}
catch {
    return 0;
};

print "Result: $result\n";

异常类

创建简单的异常类

perl
package MyException;

use strict;
use warnings;

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

sub message {
    my ($self) = @_;
    return $self->{message} || "Unknown error";
}

sub code {
    my ($self) = @_;
    return $self->{code} || 0;
}

1;

# 使用异常类
package main;

use MyException;

eval {
    die MyException->new(
        message => "Something failed",
        code => 500
    );
};

if ($@) {
    if (ref $@ eq 'MyException') {
        print "Exception: " . $@->message() . "\n";
        print "Code: " . $@->code() . "\n";
    } else {
        print "Other error: $@\n";
    }
}

使用 Exception::Class

perl
use Exception::Class (
    'MyApp::Error' => { fields => [qw(action)] },
    'MyApp::FileError' => {
        isa => 'MyApp::Error',
        alias => 'file_error'
    },
    'MyApp::NetworkError' => {
        isa => 'MyApp::Error',
        alias => 'network_error'
    }
);

# 抛出异常
file_error(
    error => "Cannot open file",
    action => "read"
);

# 捕获异常
eval {
    file_error(error => "Test error");
};

if (my $e = Exception::Class->caught('MyApp::FileError')) {
    print "File error: " . $e->error . "\n";
    print "Action: " . $e->action . "\n";
} elsif (my $e = Exception::Class->caught('MyApp::Error')) {
    print "Error: " . $e->error . "\n";
}

日志记录

使用 Log::Log4perl

perl
use Log::Log4perl;

# 配置日志
my $conf = q(
    log4perl.rootLogger=DEBUG, LOGFILE, Screen
    
    log4perl.appender.LOGFILE=Log::Log4perl::Appender::File
    log4perl.appender.LOGFILE.filename=app.log
    log4perl.appender.LOGFILE.layout=PatternLayout
    log4perl.appender.LOGFILE.layout.ConversionPattern=%d %p %m %n
    
    log4perl.appender.Screen=Log::Log4perl::Appender::Screen
    log4perl.appender.Screen.layout=PatternLayout
    log4perl.appender.Screen.layout.ConversionPattern=%d %p %m %n
);

Log::Log4perl::init(\$conf);

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 error message");

简单的日志函数

perl
sub log_message {
    my ($level, $message) = @_;
    my $timestamp = localtime();
    printf "[%s] [%s] %s\n", $timestamp, $level, $message;
}

sub log_debug { log_message('DEBUG', @_) }
sub log_info { log_message('INFO', @_) }
sub log_warn { log_message('WARN', @_) }
sub log_error { log_message('ERROR', @_) }

log_debug("Debugging information");
log_info("Processing started");
log_warn("Low disk space");
log_error("Failed to connect");

调试技巧

使用 warn 进行调试

perl
sub process_data {
    my ($data) = @_;
    
    warn "Processing data: $data\n";
    
    # ... 处理代码 ...
    
    warn "Data processed successfully\n";
}

使用 Data::Dumper

perl
use Data::Dumper;

my $complex_data = {
    users => [
        {name => "Alice", age => 25},
        {name => "Bob", age => 30}
    ]
};

# 打印数据结构
warn Dumper($complex_data);

使用 Carp

perl
use Carp;

# cluck - 带堆栈跟踪的警告
sub check_value {
    my ($value) = @_;
    cluck "Value is negative" if $value < 0;
}

# confess - 带堆栈跟踪的致命错误
sub critical_error {
    confess "Critical error occurred";
}

# carp - 调用者级别的警告
sub process {
    carp "Processing warning";
}

实践示例

示例 1:带错误处理的文件操作

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

sub read_file_safely {
    my ($filename) = @_;
    
    try {
        open(my $fh, '<', $filename) or die "Cannot open $filename: $!";
        
        my @lines = <$fh>;
        close($fh);
        
        return \@lines;
    }
    catch {
        my $error = shift;
        warn "Error reading file: $error";
        return undef;
    };
}

my $file = 'data.txt';
my $content = read_file_safely($file);

if ($content) {
    print "File read successfully\n";
    print @$content;
} else {
    print "Failed to read file\n";
}

示例 2:数据库错误处理

perl
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use Try::Tiny;

sub connect_to_database {
    my ($dsn, $user, $password) = @_;
    
    try {
        my $dbh = DBI->connect($dsn, $user, $password, {
            RaiseError => 1,
            PrintError => 0,
            AutoCommit => 0
        });
        
        return $dbh;
    }
    catch {
        my $error = shift;
        die "Database connection failed: $error";
    };
}

sub execute_query {
    my ($dbh, $query, @params) = @_;
    
    try {
        my $sth = $dbh->prepare($query);
        $sth->execute(@params);
        return $sth->fetchall_arrayref({});
    }
    catch {
        my $error = shift;
        $dbh->rollback;
        die "Query failed: $error";
    };
}

# 使用
my $dbh = connect_to_database(
    'DBI:mysql:database=test;host=localhost',
    'user',
    'password'
);

my $results = execute_query($dbh, 'SELECT * FROM users');
print Dumper($results);

$dbh->disconnect;

示例 3:Web 服务错误处理

perl
#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use Try::Tiny;
use JSON;

sub fetch_url {
    my ($url) = @_;
    
    try {
        my $ua = LWP::UserAgent->new();
        $ua->timeout(10);
        
        my $response = $ua->get($url);
        
        unless ($response->is_success) {
            die "HTTP request failed: " . $response->status_line;
        }
        
        my $data = decode_json($response->content);
        return $data;
    }
    catch {
        my $error = shift;
        warn "Error fetching URL: $error";
        return undef;
    };
}

my $api_url = 'https://api.example.com/data';
my $api_data = fetch_url($api_url);

if ($api_data) {
    print "API request successful\n";
    print Dumper($api_data);
} else {
    print "API request failed\n";
}

小结

本章节学习了 Perl 的错误处理:

  1. ✅ die 和 warn
  2. ✅ eval 错误捕获
  3. ✅ 自定义错误处理
  4. ✅ Try::Tiny 模块
  5. ✅ 异常类
  6. ✅ 日志记录
  7. ✅ 调试技巧

接下来,我们将学习 Perl 特殊变量