在现代Perl中编写异常类的最佳实践

4
使用Exception::Class,我可以将异常定义为类,并且只要它们在任何地方被加载过,它们就可以随处使用。但是现在包括E::C文档在内的许多地方都建议使用ThrowableThrowable是一个角色(role),因此我需要构建要混合(compose)进去的类。 Throwable::Factory 对此有所帮助,但我无法弄清楚如何使这些类随处可用。似乎T::F构建返回不透明类名的子例程(subroutines)。我感觉缺少最后一块拼图,但是我还没有找到T::F现实世界使用的任何示例。
2个回答

2

我需要寻找以下4个内容:

  1. 声明异常类型的简单语法。
  2. 实现Throwable的异常。
  3. 在异常对象中添加额外的功能,例如标签、自定义属性以及简化将属性传递给构造函数。
  4. 通过类进行异常实例化(全局可用),而不是使用函数(必须导入到每个模块中)。

Exception::ClassThrowable::FactoryThrowable::SugarFactory这样的工具提供了一种简洁的语法来声明异常类型,但事实证明我可以不用它们。实际上,Throwable::Factory拥有我想要的所有功能,除了异常函数必须在使用它们的同一文件中声明。它们有点像可抛弃的异常。我不想要那个。

Throwable::Factory异常中的一些额外功能来自Throwable::Error,它是Throwable分布的一部分。其余的功能很容易窃取。事实上,Throwable::Error是一个Moo类,所以我们有了一个赢家。

我可以将所有的异常类放在一个文件中,并在应用程序顶部通过use加载它。异常层次结构继承自Throwable::Error作为基类。由于这些是Moo类,因此向特定类添加自定义访问器非常简单。我可以从Throwable::Factory中复制/粘贴我喜欢的额外功能。

package MyApp::Exceptions ;
use strict ;
use warnings ;

use Throwable::Error ;
use Types::Standard qw( Str ) ;
use Moo ;
use namespace::clean ;

use feature qw(signatures) ;
no warnings qw(experimental::signatures) ;

extends 'Throwable::Error' ;

with 'Role::Identifiable::HasTags' ;

has description => ( 
    is => 'ro', 
    isa => Str, 
    required => 1, 
    default => 'Generic exception',
    ) ;
   
# stack_trace() and message() inherited from Throwable::Error 
sub error   ($self) { $self->message  }
sub package ($self) { $self->stack_trace->frame(0)->package  }
sub file    ($self) { $self->stack_trace->frame(0)->filename  }
sub line    ($self) { $self->stack_trace->frame(0)->line  }

# sugar for ::HasTags 
sub has_tags ( $self, @wanted ) {
    $self->has_tag($_) || return 0 for @wanted ;
    return 1 ;
    }

# support shorthand instantiation eg Foo->throw($message, attr => $val);
around BUILDARGS => sub {
    my ( $orig, $class, @args ) = @_ ;
    return +{} unless @args ;
    return $class->$orig(@args) if @args == 1 ;
    unshift @args, 'message' if @args % 2 ;
    return $class->$orig( {@args} ) ;
    } ;

# ----- enduser exception classes -----
package SystemError ;
use Types::Standard qw( Int ) ;
use Moo ;
extends 'MyApp::Exceptions' ;
has code           => ( is => 'ro', isa => Int->where('$_ >= 0'), default => 1 ) ;
has '+description' => ( default => 'A system error' ) ;

package FileError ;
use Types::Standard qw( InstanceOf ) ;
use Moo ;
extends 'SystemError' ;
has '+code'        => ( default => 2 ) ;
has '+description' => ( default => 'A file error' ) ;
has file           => ( is => 'ro', required => 1, isa => InstanceOf['Path::Tiny'] ) ;

1 ;

只要我在某个地方说过use MyApp::Exceptions;,那么现在我可以在任何地方都这样说:
use Nice::Try ;

try {
    something() or SystemError->throw("Problem trying to do something", 
        code => 7, 
        tags => [qw(something broke)],
        ) ;
    }

catch ( SystemError $e where { $_->has_tags(qw(something broke)) }) {
    fix_it($e) ;
    }

catch ( SystemError $e where { $_->has_tag('something') }) {
    repair_it($e) ;
    }

catch ( FileError $e ) {
    warn sprintf "Problem doing something() with file %s: %s", 
        $e->file->basename, $e->message ;
    }

catch ( $e ) {
    die "Give up! $e" ;
    }

1
我已经将这个放在Github上,网址为https://github.com/davebaird/throwable-error-somesugar。 - Dave Baird

1

一个想法是将异常收集在一个单独的模块中,并将其导入到所有需要访问异常的模块中。不幸的是,由于某种原因似乎很难导出这些异常。我尝试了以下方法 (MyExceptions.pm):

package MyExceptions;
use Throwable::Factory
  GeneralException => undef,
  RuntimeException => undef,
;
our @EXPORT = qw(GeneralException RuntimeException);
sub import2 {
    no strict 'refs';

    my $caller = caller;
    my $pkg = __PACKAGE__;
    for my $name (@EXPORT) {
        my $imported = $caller . '::' . $name;
        my $coderef = *{$pkg . "::" . $name};
        *{ $imported } = \*{ $coderef };
    }
}

sub import {
    no strict 'refs';

    my $caller = caller;
    my $pkg = __PACKAGE__;
    my @coderefs = (
        ["GeneralException", *MyExceptions::GeneralException],
        ["RuntimeException", *MyExceptions::RuntimeException]
    );
    for my $item (@coderefs) {
        my ($name, $coderef) = @$item;
        my $imported = $caller . '::' . $name;
        *{ $imported } = \*{ $coderef };
    }
}

1;

我无法让上述代码中的import2()导出器sub工作(它没有导出异常,但是导出了其他内容(但是是什么?)),因此我编写了import()子程序作为解决方法,它可以正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接