如果Moose构建器方法失败,我该怎么办?

dao*_*oad 10 perl moose

在构建器方法中处理故障的最佳方法是什么?

例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果_build_file_handle无法获得句柄,则构建器将返回undef,这会使类型约束失败.

我可以在file_handle类型约束中使用union ,以便它接受undef一个有效值.但是has_file_handle,即使值很大,谓词也会返回true undef.

有没有办法表明构建器失败了,属性应该保持清除?

jro*_*way 9

你没有想到足够高的水平.好的,构建器失败了.该属性仍未定义.但是你如何处理调用访问器的代码呢?类合同表明调用该方法总是会返回一个IO :: File.但现在它正在返回undef.(合同是IO::File,不是Maybe[IO::File]吗?)

所以在下一行代码中,调用者将会死("不能在_caller.pl第42行的未定义值上调用方法'readline'.")因为它希望你的类遵循它定义的契约.失败不是你的班级应该做的事情,但现在确实如此.调用者如何做任何事来纠正这个问题?

如果它可以处理undef,调用者实际上并不需要一个文件句柄开始...所以为什么它要求你的对象?

考虑到这一点,唯一理智的解决方案就是死亡.您不能满足您同意的合同,并且die是您摆脱这种情况的唯一方法.所以就这样做; 死亡是生活中的事实.

现在,如果您不准备在构建器运行时死亡,则需要在可能失败的代码运行时进行更改.您可以在对象构造时执行此操作,方法是将其设置为非延迟,或者通过在BUILD(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;
}
Run Code Online (Sandbox Code Playgroud)

现在你知道程序什么时候会死; in new或in write_log.你知道,因为文档说的是这样.

第二种方式使您的代码更清洁; 消费者不需要知道你的类的实现,它只需要知道它可以告诉它写一些日志消息.现在调用者不关心您的实现细节; 它只是告诉班级它真正想要它做什么.

而且,死亡write_log甚至可能是你可以恢复的东西(在一个捕获区块中),而"无法打开这个随机不透明的东西你不应该知道"对于呼叫者来说更难以恢复.

基本上,设计你的代码是理智的,异常是唯一的答案.

(无论如何,我没有得到整体"他们是一个kludge".他们在C++中的工作方式完全相同,在Java和Haskell以及其他所有语言中也是如此.这个词die真的那么可怕吗?)

  • 使用Try :: Tiny,已在多个线程中多次建议.Perl不会改变,因为你害怕Stack Overflow上的库.所以要么使用库,要编写样板,修复perl本身,要么就离开了. (3认同)

Eth*_*her 6

"最佳"是主观的,但你必须决定哪些在你的代码中更有意义:

  1. 如果在文件句柄无法构建时可以继续使用代码(即它是可恢复的条件),则构建器应返回undef并将类型约束设置为'Maybe[IO::File]'.这意味着您还必须在使用它时检查该属性的定义.您还可以检查此属性是否已正确构建BUILD,并选择在该点采取进一步操作(正如他在评论中提到的那样),例如,如果它是undef则调用clear_file_handle(因为构建器将始终为属性赋值,假设它当然不会死).

  2. 否则,让构建器失败,或者通过显式抛出异常(您可以选择向上捕获),或者只是返回undef并让类型约束失败.无论哪种方式你的代码都会死; 你可以选择它是如何死的以及堆栈轨迹是多么庞大.:)

PS.您可能还想看看Moose在内部使用的Try :: Tiny,它基本上只是*do eval { blah } or die ...成语的包装器.

*但做得对!并以一种很酷的方式!(我似乎从#moose听到很多耳语!)

  • 我不明白; 尝试:: Tiny实际上是微不足道的,并且几乎没有任何东西可以解决在Perl中几乎是一个bug的行为.我犹豫是否愿意做出像Moose一样深远的事情,但你可以在大约5分钟内阅读并理解Try :: Tiny的来源.有什么能烧你? (2认同)