10

ビルダーメソッドの失敗を処理する最良の方法は何ですか?

例えば:

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_handlean を受け入れることができます。undefただし、値が であっても、述語has_file_handleは true を返しますundef

ビルダーが失敗したことを通知する方法はありますか?属性をクリアしたままにする必要がありますか?

4

3 に答える 3

9

あなたは十分なレベルで考えていません。OK、ビルダーは失敗します。属性は未定義のままです。しかし、アクセサーを呼び出しているコードについてはどうしますか?クラスのコントラクトは、メソッドを呼び出すと常にIO::Fileが返されることを示していました。しかし今、それはundefを返しています。(契約はIO::FileそうではありませんでしMaybe[IO::File]たね?)

したがって、コードの次の行で、呼び出し元は死にます( "the_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;
}

これで、プログラムがいつ終了するかがわかりました。、newまたはでwrite_log。ドキュメントがそう言っているのであなたは知っています。

2番目の方法は、コードを非常にクリーンにします。コンシューマーは、クラスの実装について知る必要はありません。ログメッセージを書き込むように指示できることを知っている必要があります。これで、呼び出し元は実装の詳細に関心がなくなります。本当にやりたかったことをクラスに伝えるだけです。

そして、死ぬwrite_logことはあなたが(キャッチブロックで)回復できるものでさえあるかもしれませんが、「とにかくあなたが知らないはずのこのランダムな不透明なものを開くことができなかった」は発信者が回復するのがはるかに難しいです。

基本的に、コードを適切に設計し、例外が唯一の答えです。

(とにかく「彼らは恨みです」という全体を理解することはできません。C++でもまったく同じように機能し、JavaやHaskell、その他すべての言語でも非常によく似ています。この言葉dieは本当に怖いのでしょうか?)

于 2010-01-30T05:56:22.857 に答える
6

「最良」は主観的なものですが、コードでどちらがより理にかなっているかを判断する必要があります。

  1. ファイルハンドルがビルドに失敗したとき (つまり、回復可能な状態) にコードを続行できる場合、ビルダーは undef を返し、型制約を に設定する必要があります'Maybe[IO::File]'。つまり、その属性を使用するたびに、その属性が定義されているかどうかも確認する必要があります。また、この属性が で適切に構築されたかどうかを確認しBUILD、その時点でさらにアクションを実行することを選択することもできます (frido のコメントで言及されているように)。 、もちろん死なないと仮定して)。

  2. それ以外の場合は、明示的に例外をスローするか (より高いレベルでキャッチすることを選択できます)、単純に undef を返して型制約を失敗させることにより、ビルダーを失敗させます。いずれにせよ、コードは死んでしまいます。死ぬ方法とスタックトレースの量を選択するだけです。:)

PS。Moose が内部的に使用しているTry::Tinyも参照してください。これは基本的にイディオムの単なるラッパーですdo eval { blah } or die ...

*しかし、正しく行われました!そしてクールな方法で!(#moose の耳元でささやき声がたくさん聞こえるようです..)

于 2010-01-29T20:14:55.013 に答える
2

ビルダーが失敗したことを通知する方法はありますか?属性はクリアされたままにする必要がありますか?

いいえ。これは意味がありません。属性がクリアされた場合、ビルダーは起動します。ビルダー内でクリアされた場合、次の呼び出しを行ったときに起動し、クリアされた状態のままになります。何かを設定するだけで、多くの作業を無駄にします。

提案はtype-union良いものですが、ファイルハンドルと存在しないファイルハンドルという2つの根本的に異なるケースで機能できるコードを作成する必要があります。これは悪い考えのようです。

ファイルハンドルがタスクに不可欠でない場合は、オブジェクトへのアクセス権を持つ同じスコープ間でファイルハンドルが共有されていない可能性があります。この場合、オブジェクトは、オブジェクトからファイルハンドルを生成するメソッドを提供するだけです。私はこれを本番コードで行います。すべてを怠惰な属性にすることに夢中にならないでください。属性の関数であるものもあり、それらをオブジェクトにアタッチすることが常に意味があるとは限りません。

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;

};
于 2010-01-29T20:54:46.257 に答える