【问题标题】:Should I put the "eval" in the subroutine or the subroutine in the "eval"?我应该将“eval”放在子程序中还是将子程序放在“eval”中?
【发布时间】:2012-09-20 01:43:52
【问题描述】:

这两种方法中的一种必须是首选还是只是口味问题?

#!/usr/bin/env perl
use warnings;
use strict;
use DBI;

my $db = 'sqlite_db';

####################   A   ####################

sub get_database_handle {
    my ( $db ) = @_;
    my $dbh;
    eval {
        $dbh = DBI->connect( "DBI:SQLite:$db", '', '', {...} )
        or die DBI->errstr;
    };
    if ( $@ ) {
        print $@;
        return;
    }
    return $dbh;
}

DATABASES: while ( 1 ) {
    # choose a database from a list of databases
    # ...
    my $dbh = get_database_handle( $db );
    next DATABASES if not defined $dbh;
    # ...
    # do something with the database
}

####################   B   ####################

sub get_database_handle {
    my ( $db ) = @_;
    my $dbh = DBI->connect( "DBI:SQLite:$db", '', '', {...} ) 
    or die DBI->errstr;
    return $dbh;
}

DATABASES: while ( 1 ) {
    # choose a database from a list of databases
    # ...
    my $dbh;
    eval { $dbh = get_database_handle( $db ); };
    if ( $@ ) {
        print $@;
        next DATABASES;
    }
    # ...
    # do something with the database
}

【问题讨论】:

    标签: perl error-handling eval


    【解决方案1】:

    这取决于在项目的其余部分中处理错误的首选方式。

    • 如果您打算使用异常,请让您的函数抛出异常。

    • 如果您要通过条件手动处理错误,请不要抛出(eval inside)。

    我自己更喜欢例外。它们声音很大(你知道有些东西坏了!),加上通过Carp::confess/Carp::longmess$SIG{__DIE__} 的堆栈跟踪对于大型代码库来说是一个不错的奖励。

    这是一个(不)成功的故事。一两个月前,我们推出了由于未经检查的数据库处理函数的返回值而默默损坏数据的损坏代码。它生产了一天。恢复数据是一个痛苦的世界。

    【讨论】:

    • +1 提倡例外。至于“eval inside”选项,如果一个人不喜欢异常(即使在你的故事来自痛苦世界之后),一个人可能会使用 RaiseError=>0 然后根本不需要 eval。
    【解决方案2】:

    如果您的代码看起来像这样,那么使用RaiseError => 1 真的毫无意义。如果您想保留该代码布局,请改用RaiseError => 0。 (您以后可以随时使用 $dbh->{RaiseError} = 1;` 打开它。)

    sub get_database_handle {
        my ( $db ) = @_;
        return DBI->connect("DBI:SQLite:$db", '', '', {
            ...
            RaiseError => 0,
            PrintError => 1,
        });
    }
    
    for my $db ( ... ) {
        my $dbh = get_database_handle( $db )
           or next;
    
        ...
    }
    

    也就是说,我建议您继续使用RaiseError => 1,但您应该更改您的代码布局。具体来说,你应该扩大评估的范围。

    sub get_database_handle {
        my ( $db ) = @_;
        return DBI->connect("DBI:SQLite:$db", '', '', {
            ...
            RaiseError => 1,
            PrintError => 0,
        });
    }
    
    for my $db ( ... ) {
        if (!eval {    
            my $dbh = get_database_handle( $db );
    
            ...
    
            1  # No exception
        }) {
            warn("Error processing database $db: $@");
        }
    }
    

    这将捕获任何错误,而不仅仅是数据库连接错误。

    【讨论】:

    • 但是我也可以使用多个评估而不是扩大一个评估的范围?
    • 当然,如果您需要这种分辨率。但是,如果您要在每个语句周围加上eval,只需使用RaiseError => 0。这就是我提到这两种解决方案的原因。
    • 我已将我的问题发布到 new thread 的此评论中。
    【解决方案3】:

    为什么要评估?拿不到数据库句柄怎么办?

    至少,子例程应该返回一个数据库句柄或者死掉,所以那里没有 eval。

    如果在出现数据库错误时您确实有工作要做,那么在子例程之外进行 eval。不需要仅围绕子例程,可以根据您的错误处理逻辑进行更广泛的范围。

    但如果你只想终止程序并打印错误,就让异常冒泡。

    【讨论】:

    • 我可能会在整个循环迭代中进行评估,以涵盖其他类型的错误(不仅仅是数据库处理失败)。如果出现错误,您可能希望至少在一段时间内从选项列表中删除 db。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    • 2017-06-30
    • 2017-05-29
    相关资源
    最近更新 更多