代码之家  ›  专栏  ›  技术社区  ›  friedo

Perl中的异常有何突破?

  •  29
  • friedo  · 技术社区  · 15 年前

    讨论 another question 让我想知道:其他编程语言的异常系统有哪些是Perl所缺少的?

    Perl的内置异常有点 自组织的 在这种情况下,它们就像Perl5对象系统一样,有点像事后诸葛亮,而且它们重载了其他关键字。( eval die )不专门针对例外情况。

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

    eval { 
        do_something_that_might_barf();
    };
    
    if ( my $err = $@ ) { 
        # handle $err here
    }
    

    有几个CPAN模块提供了语法上的优势来添加try/catch关键字,并允许轻松声明异常类层次结构和其他内容。

    我看到Perl的异常系统的主要问题是使用 $@ 保留当前错误,而不是专用错误 catch -从范围的角度来看,类型机制可能更安全,尽管我个人从未遇到过 $@ 变得笨拙起来。

    8 回复  |  直到 9 年前
        1
  •  13
  •   Ether    15 年前

    一些异常类,例如 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代码。

    另外两个错误的“gotchas”(这两个都给我带来了悲伤,因为如果你以前没有遇到过,它们很难调试):

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

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

    1. use Error qw(:try) 被省略,所以 try {}... 块将被误放(根据代码的其余部分,您可能会看到或可能不会看到警告)
    2. catch块后缺少分号!非直观的控制块不使用分号,但实际上 try 是一个 原型方法调用 .

    哦,是的,这也提醒了我,因为 尝试 , catch etc是方法调用,这意味着这些块中的调用堆栈将不是您期望的那样。(由于内部调用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;
    };
    
        2
  •  25
  •   Michael Carman    14 年前

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

    以下是如何使用它:

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

    eval $@ 是你不需要担心的活动部件。

    有些人认为这是一个拼凑,但在阅读了其他语言(以及Perl5)的实现之后,它与其他语言没有什么不同。只有一个 $@ 移动你的手会被夹住的部分…但和其他机械零件一样,机械零件暴露在外…如果你不碰它,它就不会把你的手指扯下来。所以使用try::tiny并保持打字速度;()

        3
  •  24
  •   Ether    15 年前

    大多数人学习的处理异常的典型方法很容易丢失捕获的异常:

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

    你可以做到:

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

    这可以防止由于以下原因而丢失异常: $@ 但它仍然容易失去 $@ .

    为了确保你不会破例,当你进行评估时,你必须本地化。 $@ ;

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

    这都是细微的破损,预防需要很多深奥的样板。

    在大多数情况下,这不是问题。但我已经被真正的代码中的异常吞噬对象析构函数烧死了。调试这个问题很糟糕。

    情况显然很糟糕。看看CPAN构建的所有模块,它们提供了相当好的异常处理。

    压倒性的支持 Try::Tiny 再加上Try::Tiny不是“聪明到一半”,说服了我去尝试一下。诸如此类 TryCatch Exception::Class::TryCatch , Error 一天又一天对我来说太复杂了。Try::Tiny是朝着正确的方向迈出的一步,但我仍然没有一个轻量级的异常类可以使用。

        4
  •  9
  •   hillu    15 年前

    我最近遇到的一个问题 eval 异常机制与 $SIG{__DIE__} 处理程序。我错误地假定只有当Perl解释器通过 die() 并希望使用此处理程序记录致命事件。后来发现我把异常记录在库代码中是致命错误,这显然是错误的。

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

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

    我在这里看到的问题是 __DIE__ 处理程序用于两种相似但不同的情况。那个 $^ 变量非常像是我的一个后期附加组件。不过,我不知道这是不是真的。

        5
  •  2
  •   Kevin Panko Matthew Woodard    9 年前

    使用Perl时,语言和用户编写的异常是结合在一起的:都是 $@ . 在其他语言中,语言异常独立于用户编写的异常,并创建完全独立的流。

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

    如果有的话 My::Exception::one My::Exception::two

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

    两者都能抓住。

    记住捕获任何非用户异常 else .

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

    也可以将异常包装在Sub调用Sub来抛出它。

        6
  •  1
  •   Paul Nathan    15 年前

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

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

    这个 哲学的 问题在于,Perl5异常是栓接在一起的;它们不是从语言设计开始就构建起来的,作为Perl编写方式的一个组成部分。

        7
  •  1
  •   Brad Gilbert    15 年前

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

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

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

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

    3. 没有等价的try:…最后:…定义将被调用的代码,不管是否引发异常,例如释放资源。

      ( finally 在Perl中,很大程度上是不必要的——对象的析构函数在作用域退出后立即运行;而不是在内存压力出现时运行。这样,您就可以在析构函数中解除分配任何非内存资源,它将正常工作。)

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

      (完全错误。 die $object 同样有效 die $string )

    5. 您不能得到一个堆栈跟踪来显示异常在哪里抛出-在python中,您可以得到详细的信息,包括调用堆栈中每一行的源代码。

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

    6. 这是个难看的混蛋。

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

        8
  •  0
  •   Kevin Panko Matthew Woodard    9 年前

    不要对常规错误使用异常。只有将停止当前执行的致命问题才会消失。所有其他应在没有 die .

    示例:被调用Sub的参数验证:不要在第一个问题时死亡。检查所有其他参数,然后通过返回某些内容或警告来决定停止,并纠正错误参数,然后继续。在测试或开发模式下完成的。但可能 死亡 在生产模式下。由应用程序决定。

    JPR(我的CPAN登录)

    来自德国S凝胶的问候