在Perl 5.14或更高版本中是否仍建议使用Try :: Tiny进行异常处理?


Answers:


34

我的回答是不受欢迎的,但是我不认为Perl程序员应该尝试使用在Perl中我们称之为“异常”的极端糟糕的概念。这些本质上是边通道返回值。但是,尽管仍然迷恋于异常的想法,即使使用全局变量传递状态的所有复杂性,人们仍在努力使它起作用。

然而,实际上,人们习惯于die发出故障信号。有人会说您可以die使用引用并传回错误对象,但是您不需要这样做die。我们有对象,所以我们应该利用对象的所有力量:

 sub some_sub {
    ...
    return Result->new( error => 1, description => ... ) if $something_went_wrong;
    return Result->new( error => 0, ... );
    }

 my $result = some_sub( ... );
 if( $result->is_error ) { ... };

这不涉及全局变量,远距离动作,解决头痛问题或不需要特殊内容。您可以创建一个微型类Result(或任何您想调用的类)来包装返回值,以便拥有结构化数据,而不是没有标识的单个值。毫无疑问,返回值意味着什么。这undef是真实的价值还是失败的征兆?如果定义了返回值或返回值正确,返回值是否正确?您的对象可以告诉您这些事情。并且,您可以将同一个对象用于die。如果您已经在使用对象die并将其用作返回值,则几乎没有建议您要忍受的所有额外工作$@

我在“返回错误对象而不是引发异常”中讨论了更多

但是,我知道您无能为力,因此您仍然必须假装Perl有例外。


2
我同意,但是正如您在最后所说的,有一些模块die在不应该使用的时候,所以我们仍然需要知道哪种机制可以捕获那些异常。未来的模块设计师,请考虑这种方法!
乔尔·伯杰

6
很好的解决方案,但是die / exception有一个很大的优点:通过子调用堆栈进行传播。我的意思是:不要测试某些子例程调用的通过或失败-只是不要捕获异常。它会传播,直到有人抓住它。
msztolcman 2012年

2
这种传播的东西是设计程序的一种非常糟糕的方式。那些更高级别的错误处理方法是什么?在Perl中,您将无法处理它并从上次中断的地方继续操作,因此您根本无法真正处理它。我展示的方法也可以传播。每个级别都可以添加到它获得和传递的结果中。
brian d foy 2012年

8
很穷?我不这么认为。错误应该在那里处理,我们可以在其中说出处理方法。许多错误不应该处理-仅记录下来,并向用户显示一些消息(这必须在GUI中,而不是在最低级别的功能中)。这只是设计假设,并且有一些优点和缺点(一如既往)。
msztolcman 2012年

6
我能理解为什么要提出自己的观点,但这要求程序员永远不要忘记检查错误。我们知道他们应该这样做,但是在紧迫的期限内,我们在理论上应该做的事情和在实践中所要做的事情是不一样的。因此,我们可以忘记检查为一个严重的错误,并有我们的代码继续,愉快地不知道它无处不在破坏它的数据。
奥维德(Ovid)

31

总是个人喜好。你比较喜欢哪个

my $rv;
if (!eval { $rv = f(); 1 } ) {
   ...
}

要么

my $rv = try {
   f();
} catch {
   ...
};

但是请记住,后者使用了匿名子,因此与return,以及next类似的东西弄混了。Try :: Tiny的try-catch最终可能会变得更加复杂,因为您在catch块与外部之间添加了通信通道。

返回异常的最佳情况(最简单)是,如果$rv没有异常,则if始终为true。看起来如下:

my $rv;
if ($rv = eval { f() }) {
   ...
   return;
}

my $rv = try {
   f();
} catch {
   ...
};

if (!$rv) {
   return;
}

这就是为什么我要使用TryCatch而不是Try :: Tiny来使用这样的模块的原因。

对Perl的更改只是意味着您可以if ($@)再次执行。换一种说法,

my $rv;
if (!eval { $rv = f(); 1 } ) {
   ...
}

可以写

my $rv = eval { f() };
if ($@) {
   ...
}

4
如果您不需要捕获eval返回的值(至少对我来说是最常见的情况)-那么eval版本会变得相当简单。
zby 2012年

与Try :: Tiny相比,TryCatch的警告要少得多,而且速度要快得多。有一个警告,加载的速度非常慢。
Schwern 2014年

@Schwern,基于Devel :: CallParser的解决方案应加载得更快。
ikegami 2014年

@ikegami不,问题在于它依赖Moose和Parse :: Method :: Signatures(MooseX :: Method :: Signatures的胆量)。
Schwern 2014年

@Schwen,这就是为什么Devel :: CallParser会提供帮助。它将允许您使用perl的内置解析功能。
ikegami 2014年

14

如果没什么,Try::Tiny语法糖还是不错的。如果您想要一些更重量级的东西,还可以使用TryCatch,它解决了一些与以下事实有关的问题:inTry::Tiny中的子句是子例程(例如,它return不包含封闭函数)。


11

Try::Tiny简单轻巧。太容易了。我们有两个问题:

  • 匿名订阅-return内部的' '语句始终存在问题
  • 始终捕捉一切

因此,我对进行了一些更改Try::Tiny,这对我们有所帮助。现在我们有:

try sub {},
catch 'SomeException' => sub {},
catch [qw/Exception1 Exception2/] => sub {},
catch_all sub {};

我知道-这种语法有点奇怪,但是由于有了明显的' sub',我们的程序员现在知道' return'语句仅从异常处理程序中退出,并且我们始终只捕获我们要捕获的异常:)


1
请注意,您可以sub{}perl -MTry::Tiny -E"&try(sub { say 'A'; die qq{B\n} if $ARGV[0] }, &catch(sub { print; }));" 1
原样

2
但更重要的是,请注意,其他模块(例如TryCatch)使用实际块,而不是匿名子块,以避免产生噪音。
ikegami 2012年

1
有没有办法Perl中捕获的一切,除非你写的代码,重新抛出不被识别的:)例外
霍布斯

2
@hobbs:当然:)但是,如果在没有您参与的情况下进行重新抛出……会更好;)这里最重要的是使用另一个关键字来捕获所有异常,然后再捕获其中的少数异常:)
msztolcman

3
取决于你的意思是光。在CPU方面,Try :: Tiny损失惨重。另一方面,TryCatch具有更复杂的依赖性。
ikegami 2012年

0

要么做:

local $@;
eval { … }

…以防止对$ @的更改影响全局范围,或使用Try :: Tiny。

从句法上讲,在某些情况下我更喜欢其中一个。


0

Try :: Tiny很不错,但是在最后一个花括号处需要使用半冒号,并且不允许使用异常变量赋值,更不用说捕获异常类了。TryCatch曾经做得很好,但是已被Devel :: Declare的新版本0.006020破坏了。另一个很棒的实现是Syntax :: Keyword :: Try,但是它没有实现异常变量分配或捕获异常类。

有一个新模块Nice :: Try,它是一个完美的替代品。

没有必要在最后一个括号上使用半冒号,例如Try :: Tiny。

您也可以像这样进行异常变量分配

  try
  {
    # something
  }
  catch( $e )
  {
    # catch this in $e
  }

它也可以使用类异常来工作,例如

  try
  {
    # something
  }
  catch( Exception $e )
  {
    # catch this in $e
  }

并且它也支持finally。它的功能集使其非常独特。

全面披露:TryCatch损坏时,我已经开发了此模块。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.