代码之家  ›  专栏  ›  技术社区  ›  daotoad

如果Moose builder方法失败,我该怎么办?

  •  10
  • daotoad  · 技术社区  · 16 年前

    在生成器方法中处理失败的最佳方法是什么?

    例如:

    package MyObj;
    use Moose;
    use IO::File;
    
    has => 'file_name'   ( is => 'ro', isa => 'Str',      required   =>1  );
    has => 'file_handle' ( is => 'ro', isa => 'IO::File', lazy_build => 1 );
    
    sub _build_file_handle {
        my $self = shift;
        my $fh = IO::File->new( $self->file_name, '<' );
    
        return $fh;
    }
    

    如果 _build_file_handle 未能获得句柄,生成器将返回 undef ,它不符合类型约束。

    file_handle 未定义 作为有效值。但是,谓词 has_file_handle 未定义

    3 回复  |  直到 16 年前
        1
  •  6
  •   Ether    16 年前

    “最佳”是主观的,但您必须决定哪一个在代码中更有意义:

    1. 'Maybe[IO::File]' . 这意味着您在使用该属性时还必须检查该属性的定义性。您还可以检查此属性是否正确内置 BUILD ,并选择在这一点上采取进一步的行动(如friedo在其评论中所暗示的),例如,如果属性未定义,则调用clear_file_handle(因为构建器将始终为属性指定一个值,假设它当然不会死)。

    2. 否则,让构建器失败,要么显式抛出异常(您可以选择捕获更高级别的异常),要么简单地返回unde并让类型约束失败。无论哪种方式,你的代码都会死掉;您只需选择它的消亡方式和堆栈跟踪的容量即可。:)

    另外,你可能还想看看 Try::Tiny ,驼鹿在内部使用,并且 基本上只是一个包装 do eval { blah } or die ... 习语

    * 但是做对了!以一种冷静的方式(我似乎听到很多来自驼鹿的耳语。)

        2
  •  9
  •   jrockway    16 年前

    你的思维水平不够高。好的,生成器失败了。该属性仍然未定义。但是您如何处理调用访问器的代码呢?类的契约表示调用该方法将始终返回IO::文件。但现在它正在返回undef(合同是 IO::File 不 Maybe[IO::File] (是吗?)

    因此,在下一行代码中,调用者将死亡(“无法对_caller.pl第42行的未定义值调用方法'readline'),因为它希望您的类遵循它定义的约定。失败不是你们班应该做的,但现在它做了。呼叫方如何解决此问题?

    如果它能处理 undef ,调用方实际上不需要文件句柄来开始。。。那它为什么要你的目标呢?

    考虑到这一点,唯一明智的解决办法就是死亡。你不能履行你同意的合同,而且 die

    现在,如果您不准备在构建器运行时死掉,那么您需要在可能失败的代码运行时进行更改。您可以在对象构造时执行此操作,方法是使其非惰性,或者在构建中显式激活属性( BUILD { $self->file_name }

    更好的选择是根本不向外界公开文件句柄,而是执行以下操作:

    # dies when it can't write to the log file
    method write_log {
        use autodie ':file'; # you want "say" to die when the disk runs out of space, right?
        my $fh = $self->file_handle;
        say {$fh} $_ for $self->log_messages;
    }
    

    现在你知道程序什么时候会死;在里面 new ,或 write_log

    第二种方法使您的代码更加干净;使用者不需要知道类的实现,只需要知道它可以告诉它编写一些日志消息。现在调用方不关心您的实现细节;它只是告诉学生它真正想要做什么。

    写日志

    (无论如何,我并不完全明白“他们是个混球”。它们在C++中工作的方式完全相同,在java和Haskell和其他语言中也非常相似。这个词是什么 真的有那么可怕吗?)

        3
  •  2
  •   Evan Carroll    16 年前

    不。这没有意义,如果属性被清除,构建器将触发,如果属性在构建器中被清除,它将在您下一次调用它时触发,并保持清除状态。浪费了很多工作,只是为了 如果有效,就设置,如果无效,则继续 .

    这个 type-union

    如果文件句柄对任务不是必需的,那么它可能不会在具有对象访问权限的同一范围内共享。如果是这种情况,那么对象只能提供一个从对象生成文件句柄的方法。我在生产代码中这样做。不要把所有东西都变成懒惰属性,有些东西是属性的函数,把它们附加到对象上并不总是有意义的。

    sub get_fh {                                                                
      my $self = shift;                                                         
    
      my $abs_loc = $self->abs_loc;                                             
    
      if ( !(-e $abs_loc) || -e -z $abs_loc ) {                                 
        $self->error({ msg => "Critical doesn't exist or is totally empty" });  
        die "Will not run this, see above error\n";                             
      }                                                                         
    
      my $st = File::stat::stat($abs_loc);                                      
      $self->report_datetime( DateTime->from_epoch( epoch => $st->mtime ) );    
    
      my $fh = IO::File->new( $abs_loc, 'r' )                                   
        || die "Can not open $abs_loc : $!\n"                                   
      ;                                                                         
    
      $fh;                                                                      
    
    }                                                                           
    

    一种完全不同的方法是使用子类 IO::File

    package DM::IO::File::InsideOut;
    use feature ':5.10';
    use strict;
    use warnings;
    
    use base 'IO::File';
    
    my %data;
    
    sub previouslyCreated {
      $data{+shift}->{existed_when_opened}
    }
    
    sub originalLoc {
      $data{+shift}->{original_location}
    }
    
    sub new {
      my ( $class, @args ) = @_;
    
      my $exists = -e $args[0] ? 1 : 0;
    
      my $self = $class->SUPER::new( @args );
    
      $data{$self} = {
        existed_when_opened => $exists
        , original_location => $args[0]
      };
    
      $self;
    
    };