Perl 意外行为:croak 与 try catch

Perl unexpected behavior: croak vs. try catch

我看到了一些指向 catch 块本身(结束)的异常(参见下面的示例)。

在我看来,这是一个意外的行为,因为它改变了原始异常的位置并且难以调试(应该说死在第 13 行。)

它显示(正确的)第 13 行,如果我使用 die/confess 或使用 eval 而不是 try-catch。

不知道我的代码将如何在堆栈中被调用,我现在开始避免使用 croak。你怎么看?我做对了吗,或者有办法改进吗?

此致,史蒂夫

use Carp;
use Try::Tiny;

try {
  foo();
} 
catch {
  # do something before die
  die $_;
};             # this is line 10

sub foo {
  croak 'die'; # this is line 13
}

输出: die at line 10.

这是 Carp

的预期行为

[...] use carp() or croak() which report the error as being from where your module was called. [...] There is no guarantee that that is where the error was, but it is a good educated guess.

所以报错在调用模块的sub的地方,这正是用户想要的

use warnings;
use strict;
use feature 'say';

use Try::Tiny;

package Throw {
    use warnings;
    use Carp qw(croak confess);

    #sub bam { die "die in module" };     # l.11 
    sub bam { croak "croak in module" };  
    1;  
};

try {
    Throw::bam();    # l.17
}
catch {
    say "caught one:   $_"; 
    die "die in catch: $_";
};    
say "done";

版画

caught one:   croak in module at exceptions.pl line 17.

die in catch: croak in module at exceptions.pl line 17.

如果子程序使用 die 抛出异常,那么会在 line 11 中报告,die 的正常行为是什么,以及您所期望的。

如果其中任何一个不清楚或次优,那么最好使用 confess 并很好地获得完整的堆栈跟踪。此外,如果您希望更多基于异常的代码行为,可以将 exception/error class 放在一起并抛出其对象, 根据需要设计和填充。

如果要confess一个对象请注意,此时Carp has limits用那个

The Carp routines don't handle exception objects currently. If called with a first argument that is a reference, they simply call die() or warn(), as appropriate.

一种方法是 confess 对象的字符串化, 至少获得完整的堆栈回溯和对象中的任何内容。


通过替换上面的 try-catch 和 $_,我得到了与 eval 相同的行为

eval { 
    Throw::bam();
};
if ($@) { 
    say "caught one:   $@"; 
    die "die in catch: $@";
};

打印出来和上面完全一样


虽然上面很清楚并且表现符合预期,但在问题的示例中确实看到了一件奇怪的事情:错误是从整个 try-catch 语句报告的,即。在它的右大括号处,第 10 行所在的位置。 (try sub 是原型,整个 try-catch 是一种语法辅助,相当于对 try 的调用采用匿名 sub,然后可能更多。请参阅 ikegami 的评论和文档。另请参阅 了解更多关于它的语法。)

这很奇怪,因为对 croaking sub 的调用是 foo()try 语句中,应该报告这一行,运行 脚本可以确认 运行 -MCarp::Always。但是在这个答案的代码中确实报告了对 Throw::bam 的调用行——为什么会有这种差异?

croak 的明确目的是在库中使用,以便用户可以看到他们(用户)代码中的哪个点以触发错误的方式调用了库。 (虽然 die 会指向检测到错误的地方,所以 库中,很可能对用户没有用。但是阅读 dieCarp 相关复杂性的文档。)

不明显的是,当 croak 在与 try-catch 在其自己的命名空间 (Try::Tiny) 中的相同命名空间 (main::foo()) 中发出时,事情得到困惑,并报告其声明的结尾。这可以通过在我上面的代码中添加 foo() 并调用它(而不是模块中的 sub)来检查,我们会重现问题的行为。

如果 main::foo()croak 是从 main:: 中的(复杂)语句调用的,则不会发生这种情况,所以这似乎是由于 try-catch名称空间的混合。 (另一方面,try-catch 糖向调用堆栈添加了一个匿名子,这肯定也会混淆。)

实际上,我会说:始终在模块外使用 croak(否则使用 die),或者,如果您想模仿基于异常的代码,则更好,使用 confess and/or 你的异常 class 层次结构。


甚至就像die ExceptionClass->new(...);

请记住,在异常方式中,Perl 只有孤独的 dieeval。对于更多结构,您需要全部实现,或使用 Exception::Class or Throwable

之类的框架

通过编写和使用一种方法,该方法生成带有来自对象的有用信息的纯字符串,用于 Carp::confess $obj->stringify.

或者通过 overloading class 的 "" (引用)运算符,因为它在 confess 对象(字符串上下文)时被使用,对于 Carp::confess $obj;无论如何,这很好。

两者的基本示例:

use overload ( q("") => \&stringify );

sub stringify { 
    my $self = shift; 
    join ", ", map { "$_ => " . ( $self->{$_} // 'undef' ) } keys %$self 
}

可以直接写一个匿名 sub.

而不是对命名子的引用

作为解决 OP 问题的一种方法,但是使用不同的模块,如果您改用 Nice::Try,您将得到您期望的结果:

use Carp;
use Nice::Try;

try {
  foo();
} 
catch {
  # do something before die
  die $_;
}             # this is line 10

sub foo {
  croak 'die'; # this is line 13
}

你得到:

die at ./try-catch-and-croak.pl line 13.
    main::foo() called at ./try-catch-and-croak.pl line 4
    main::__ANON__ called at ./try-catch-and-croak.pl line 7
    eval {...} called at ./try-catch-and-croak.pl line 7    ...propagated at ./try-catch-and-croak.pl line 9.

为了全面披露,我是背后的作者 Nice::Try