【问题标题】:Best practice for writing exception classes in modern Perl在现代 Perl 中编写异常类的最佳实践
【发布时间】:2021-12-12 04:53:22
【问题描述】:

使用Exception::Class,我可以将异常定义为类,一旦它们被加载到任何地方,它们就可以在任何地方使用。但是现在很多地方,包括 E::C 本身的文档,都推荐使用 Throwable

Throwable 是一个角色,所以我需要构建类来组合它。 Throwable::Factory 对此有所帮助,但我不知道如何让这些类随处可用。似乎 T::F 构建了返回不透明类名的子例程。我觉得我错过了最后一块拼图,但找不到任何 T::F 实际使用的例子。

【问题讨论】:

    标签: perl exception


    【解决方案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() 子哪个有效。

    【讨论】:

      【解决方案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" ;
          }
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-25
      • 2018-01-03
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      • 2011-08-12
      相关资源
      最近更新 更多