Perl中的异常有什么问题?

时间:2010-01-29 21:06:18

标签: perl exception syntax exception-handling

another question中的讨论让我想知道:Perl缺少其他编程语言的异常系统有什么作用?

Perl的内置异常有点像 ad-hoc ,因为它们就像Perl 5对象系统一样,在事后的想法中加以分类,并且它们会超载其他关键字({{ 1}}和eval)并非专门用于例外。

与具有内置try / throw / catch类型语法的语言相比,语法可能有点难看。我通常这样做:

die

有几个CPAN模块提供语法糖来添加try / catch关键字,并允许轻松声明异常类层次结构等等。

我在Perl的异常系统中看到的主要问题是使用特殊的全局eval { do_something_that_might_barf(); }; if ( my $err = $@ ) { # handle $err here } 来保存当前错误,而不是一个可能更安全的专用$@类型机制透视,虽然我从来没有亲自遇到catch获得任何问题。

8 个答案:

答案 0 :(得分:25)

Try::Tiny(或构建在其上的模块)是处理Perl 5中异常的唯一正确方法。涉及的问题很微妙,但链接的文章详细解释了它们。

以下是如何使用它:

use Try::Tiny;

try {
    my $code = 'goes here';
    succeed() or die 'with an error';
}
catch {
    say "OH NOES, YOUR PROGRAM HAZ ERROR: $_";
};

eval$@是您不需要关注的活动部分。

有些人认为这是一个kludge,但是在阅读了其他语言(以及Perl 5)的实现之后,它与其他语言没有什么不同。只有$@移动部件,你可以让你的手被抓住...但是与其他具有外露活动部件的机器一样......如果你不碰它,它就不会扯掉你的手指因此,请使用Try :: Tiny并保持打字速度;)

答案 1 :(得分:24)

大多数人学会处理异常的典型方法很容易丢失被困异常:

eval { some code here };
if( $@ ) {  handle exception here };

你可以这样做:

eval { some code here; 1 } or do { handle exception here };

这可以避免由于$@被破坏而遗漏异常,但仍然容易失去$@的值。

为了确保不破坏异常,当您执行eval时,您必须本地化$@;

eval { local $@; some code here; 1 } or do { handle exception here };

这是微妙的破损,预防需要大量深奥的样板。

在大多数情况下,这不是问题。但是我被实际代码中的异常吃掉对象析构函数所灼伤。调试问题很糟糕。

情况显然很糟糕。看看CPAN上构建的所有模块都提供了不错的异常处理。

赞成Try::Tiny的压倒性反应加上Try :: Tiny不是“太聪明一半”的事实,已经说服我尝试一下。像TryCatchException::Class::TryCatchError以及on和on这样的东西太复杂了,我无法相信。 Try :: Tiny是朝着正确方向迈出的一步,但我仍然没有使用轻量级异常类。

答案 2 :(得分:13)

一些例外类,例如Error,无法处理try / catch块中的流量控制。这会导致细微的错误:

use strict; use warnings;
use Error qw(:try);

foreach my $blah (@somelist)
{
    try
    {
        somemethod($blah);
    }
    catch Error with
    {
        my $exception = shift;
        warn "error while processing $blah: " . $exception->stacktrace();
        next;    # bzzt, this will not do what you want it to!!!
    };

    # do more stuff...
}

解决方法是使用状态变量并检查try / catch块之外的内容,对我看起来非常像臭臭的n00b代码。

错误中的另外两个“陷阱”(这两个都让我感到悲伤,因为如果你之前没有碰到它们,它们很难调试):

use strict; use warnings;
try
{
    # do something
}
catch Error with
{
    # handle the exception
}

看起来很明智,对吗?此代码编译,但会导致奇怪和不可预测的错误。问题是:

  1. use Error qw(:try)被省略,因此try {}...块将被错误地取消(您可能会看到或未看到警告,具体取决于您的其他代码)
  2. 在拦截后丢失分号!非直观的控制块不使用分号,但实际上try原型方法调用
  3. 哦,是的,这也提醒我,因为trycatch等是方法调用,这意味着这些块中的调用堆栈将不是您所期望的。 (实际上有两个额外的堆栈级别,因为Error.pm内部有一个内部调用。)因此,我有一些模块这样的模板代码,这只会增加杂乱:

    my $errorString;
    try
    {
        $x->do_something();
        if ($x->failure())
        {
            $errorString = 'some diagnostic string';
            return;     # break out of try block
        }
    
        do_more_stuff();
    }
    catch Error with
    {
        my $exception = shift;
        $errorString = $exception->text();
    }
    finally
    {
        local $Carp::CarpLevel += 2;
        croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString;
    };
    

答案 3 :(得分:9)

我最近遇到的eval异常机制问题与$SIG{__DIE__}处理程序有关。我错了 - 假设只有通过die()退出Perl解释器并且想要使用此处理程序记录致命事件时才会调用此处理程序。然后我发现我在库代码中记录异常是致命错误,这显然是错误的。

解决方案是检查$^S$EXCEPTIONS_BEING_CAUGHT变量的状态:

use English;
$SIG{__DIE__} = sub {
    if (!$EXCEPTION_BEING_CAUGHT) {
        # fatal logging code here
    }
};

我在这里看到的问题是__DIE__处理程序在两个相似但不同的情况下使用。那个$^S变量看起来像是一个迟到的加载项。不过,我不知道是不是真的如此。

答案 4 :(得分:2)

使用Perl,可以组合语言和用户编写的异常:两者都设置为$@。在其他语言中,语言异常与用户编写的异常分开,并创建一个完全独立的流程。

您可以捕获用户编写的异常的基础。

如果有My::Exception::oneMy::Exception::two

if ($@ and $@->isa('My::Exception'))

会抓住两者。

请记住使用else抓住任何非用户例外。

elsif ($@)
    {
    print "Other Error $@\n";
    exit;
    }

将异常包装在子调用sub中以抛出它也很好。

答案 5 :(得分:1)

在C ++和C#中,您可以定义可以抛出的类型,并使用单独的catch块来管理每种类型。根据我在chomatic博客上所读到的内容,Perl类型系统有一些与RTTI和继承有关的问题。

我不确定其他动态语言如何管理异常; C ++和C#都是静态语言,并且在类型系统中具有一定的功能。

哲学问题是Perl 5的异常是用螺栓固定的;它们不是从语言设计的开始构建的,而是Perl编写的必要条件。

答案 6 :(得分:1)

自从我使用Perl以来已经过了很长时间,所以我的记忆可能很模糊和/或Perl可能已经改进,但我记得(与我每天使用的Python相比):

  1. 由于异常是后期添加,因此核心库中不一致支持它们

    (不是这样;它们在核心库中不一致支持,因为编写这些库的程序员不喜欢异常。)

  2. 没有预定义的异常层次结构 - 您无法通过捕获基类来捕获相关的异常组

  3. 没有相应的try:... finally:...定义将被调用的代码,无论是否引发异常,例如:释放资源。

    (Perl中的finally在很大程度上是不必要的 - 对象的析构函数在作用域退出后立即运行;而不是每当发生内存压力时都会运行。所以你实际上可以解析析构函数中的任何非内存资源,它会很有效。)

  4. (据我所知)你只能抛出字符串 - 你不能抛出有附加信息的对象

    (完全错误。die $objectdie $string的效果一样。)

  5. 你不能得到一个堆栈跟踪显示抛出异常的位置 - 在python中你会得到详细的信息,包括调用堆栈中每一行的源代码

    (错误。perl -MCarp::Always并享受。)

  6. 这是一个丑陋的kludge。

    (主观。它在Perl中以与其他地方相同的方式实现。它只使用不同命名的关键字。)

答案 7 :(得分:0)

不要将异常用于常规错误。只有会阻止当前执行的致命问题才会消亡。所有其他应该在没有die的情况下处理。

示例:被调用子的参数验证:不要在第一个问题时死掉。检查所有其他参数,然后通过返回某些内容或警告并更正故障参数并继续进行决定停止。那是在测试或开发模式下。但在生产模式下可能die。让应用程序决定这一点。

JPR(我的CPAN登录)

来自德国Sögel的问候