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
会指向检测到错误的地方,所以 在 库中,很可能对用户没有用。但是阅读 die
和 Carp
相关复杂性的文档。)
不明显的是,当 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 只有孤独的 die
和 eval
。对于更多结构,您需要全部实现,或使用 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
我看到了一些指向 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()
orcroak()
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 calldie()
orwarn()
, 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
会指向检测到错误的地方,所以 在 库中,很可能对用户没有用。但是阅读 die
和 Carp
相关复杂性的文档。)
不明显的是,当 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 只有孤独的 die
和 eval
。对于更多结构,您需要全部实现,或使用 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