web-dev-qa-db-ja.com

ベストプラクティス:プロパティから例外をスローする

プロパティゲッターまたはセッター内から例外をスローするのが適切な場合適切でない場合どうして?このテーマに関する外部ドキュメントへのリンクは役に立ちます... Googleは驚くほど少ししか見つかりませんでした。

102
Jon Seigel

Microsoftは、プロパティの設計方法に関する推奨事項を http://msdn.Microsoft.com/en-us/library/ms229006.aspx で提供しています。

基本的に、プロパティゲッターは常に安全に呼び出すことができる軽量のアクセサーにすることを推奨します。例外がスローする必要がある場合、メソッドになるようにゲッターを再設計することをお勧めします。セッターの場合、例外は適切で許容可能なエラー処理戦略であることを示します。

インデクサーについては、ゲッターとセッターの両方が例外をスローすることは容認できるとマイクロソフトは示しています。実際、.NETライブラリの多くのインデクサーがこれを実行します。最も一般的な例外はArgumentOutOfRangeExceptionです。

プロパティゲッターで例外をスローしたくない理由はいくつかあります。

  • プロパティはフィールドのように「表示」されるため、(設計による)例外をスローできることは必ずしも明らかではありません。一方、メソッドを使用すると、プログラマは、例外がメソッドを呼び出すことで予期される結果であるかどうかを予測および調査するようにトレーニングされます。
  • ゲッターは、シリアライザーやデータバインディング(WinFormsやWPFなど)など、多くの.NETインフラストラクチャで使用されます。このようなコンテキストでの例外の処理は、すぐに問題になります。
  • プロパティゲッターは、オブジェクトを監視または検査すると、デバッガーによって自動的に評価されます。ここでの例外は混乱を招き、デバッグ作業を遅くする可能性があります。同じ理由で、プロパティで他の高価な操作(データベースへのアクセスなど)を実行することも望ましくありません。
  • 多くの場合、プロパティは連鎖規則で使用されます:obj.PropA.AnotherProp.YetAnother-この種の構文では、例外catchステートメントを挿入する場所を決定することが問題になります。

副次的な注意として、プロパティが例外をスローするように設計されていないためであるということに注意する必要があります。それは簡単に呼び出しコードである可能性があります。新しいオブジェクト(文字列など)を割り当てるという単純な行為でも、例外が発生する可能性があります。常に防御的にコードを記述し、呼び出すものから例外を予期する必要があります。

123
LBushkin

セッターから例外をスローしても何も問題はありません。結局のところ、値が特定のプロパティに対して無効であることを示すより良い方法は何でしょうか?

ゲッターの場合、一般的に眉をひそめられますが、それは非常に簡単に説明できます。一般に、プロパティゲッターはオブジェクトの現在の状態を報告します。したがって、ゲッターがスローするのが妥当である唯一のケースは、状態が無効な場合です。しかし、一般に、最初に無効なオブジェクトを取得したり、通常の手段で無効な状態にしたりできないようにクラスを設計することをお勧めします(つまり、コンストラクターで常に完全に初期化すること、および状態の有効性とクラスの不変条件に関して、メソッドを例外に対して安全にしてみてください)。そのルールに固執している限り、プロパティゲッターは無効な状態を報告しなければならない状況に陥ってはならず、したがってスローすることはありません。

私が知っている例外が1つありますが、実際にはかなり大きな例外です。IDisposableを実装するオブジェクトです。 Disposeは特にオブジェクトを無効な状態にする方法として意図されており、その場合に使用される特別な例外クラスObjectDisposedExceptionもあります。オブジェクトが破棄された後、プロパティゲッターを含む(およびObjectDisposedException自体を除く)クラスメンバーからDisposeをスローすることは完全に正常です。

34
Pavel Minaev

ゲッターではほとんど適切ではなく、セッターでは適切な場合もあります。

これらの種類の質問に最適なリソースは、CwalinaとAbramsによる「Framework Design Guidelines」です。製本として入手でき、その大部分はオンラインでも入手できます。

セクション5.2から:プロパティの設計

プロパティゲッターから例外をスローすることは避けてください。プロパティゲッターは単純な操作である必要があり、前提条件はありません。ゲッターが例外をスローできる場合、おそらくメソッドに再設計する必要があります。このルールはインデクサーには適用されないことに注意してください。インデクサーでは、引数の検証の結果として例外が発生します。

このガイドラインはプロパティゲッターにのみ適用されることに注意してください。プロパティセッターで例外をスローしてもかまいません。

24
Eric Lippert

例外に対する素晴らしいアプローチの1つは、次のように、自分自身や他の開発者向けにコードを文書化するためにそれらを使用することです。

例外は、例外的なプログラム状態である必要があります。これは、あなたが望むところならどこにでも書いて良いということです!

それらをゲッターに配置する理由の1つは、クラスのAPIを文書化することです。プログラマが間違った使い方をしようとするとすぐにソフトウェアが例外をスローした場合、間違った使い方をしないでしょう。たとえば、データ読み取りプロセス中に検証がある場合、データに致命的なエラーがあった場合、プロセスの結果に継続してアクセスできるとは意味がありません。この場合、他のプログラマーがこの状態をチェックすることを保証するエラーがあった場合、出力をスローすることができます。

これらは、サブシステム/メソッド/その他の前提と境界を文書化する方法です。一般的なケースでは、それらを捕まえてはいけません!これは、システムが期待どおりに連携して動作している場合にスローされないためでもあります。例外が発生した場合、コードの前提が満たされていないことを示します。たとえば、周囲の世界とやり取りしていないそれはもともと意図されていました。この目的のために作成された例外をキャッチした場合、システムが予測不能/一貫性のない状態になったことを意味する可能性があります-これにより、最終的にデータまたは同様のもののクラッシュまたは破損が発生し、検出/デバッグがはるかに困難になる可能性があります。

例外メッセージはエラーを報告する非常に大雑把な方法です-それらをまとめて収集することはできず、実際には文字列のみが含まれます。このため、入力データの問題を報告するのには適していません。通常の実行では、システム自体はエラー状態になりません。このため、それらのメッセージはユーザー向けではなくプログラマー向けに設計する必要があります。入力データに誤りがあるものを発見し、より適切な(カスタム)形式でユーザーに中継できます。

このルールの例外(haha!)は、IOのようなものです。例外は制御できないため、事前に確認することはできません。

2
JonnyRaa

これはすべてMSDNに文書化されています(他の回答にリンクされています)が、一般的な経験則は次のとおりです...

セッターで、タイプを超えてプロパティを検証する必要がある場合。たとえば、PhoneNumberというプロパティにはおそらく正規表現の検証が必要であり、形式が有効でない場合はエラーをスローする必要があります。

ゲッターの場合、場合によっては値がnullですが、それはおそらく(設計ガイドラインに従って)呼び出しコードで処理したいことです。

1
David

MSDN:標準の例外タイプをキャッチしてスローする

http://msdn.Microsoft.com/en-us/library/ms229007.aspx

0
smack0007

このコードは、どの例外をスローするのかわからない場所にあります。

public Person
{
    public string Name { get; set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    if (person.Name == null) {
        throw new Exception("Name of person is null.");
        // I was unsure of which exception to throw here.
    }

    Console.WriteLine("Name is: " + person.Name);
}

モデルをコンストラクターの引数として強制することにより、そもそもモデルのプロパティがnullになるのを防ぎました。

public Person
{
    public Person(string name)
    {
        if (name == null) {
            throw new ArgumentNullException(nameof(name));
        }
        Name = name;
    }

    public string Name { get; private set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    Console.WriteLine("Name is: " + person.Name);
}
0
Fred

これは非常に複雑な質問であり、答えはオブジェクトの使用方法によって異なります。経験則として、「遅延バインディング」であるプロパティゲッターとセッターは例外をスローするべきではありませんが、「早期バインディング」を持つプロパティは必要に応じて例外をスローする必要があります。ところで、Microsoftのコード分析ツールは、私の意見ではプロパティの使用を狭すぎると定義しています。

「遅延バインディング」とは、プロパティがリフレクションによって検出されることを意味します。たとえば、Serializeable」属性は、プロパティを介してオブジェクトをシリアル化/逆シリアル化するために使用されます。この種の状況で例外をスローすると、壊滅的な方法で物事が壊れます。

「アーリーバインディング」とは、コンパイラによってプロパティの使用がコード内でバインドされることを意味します。たとえば、記述するコードがプロパティゲッターを参照する場合。この場合、例外が意味をなす場合に例外をスローしても問題ありません。

内部属性を持つオブジェクトには、それらの属性の値によって決定される状態があります。オブジェクトの内部状態を認識し、敏感な属性を表すプロパティは、遅延バインディングに使用しないでください。たとえば、開いてアクセスし、閉じなければならないオブジェクトがあるとします。この場合、最初にopenを呼び出さずにプロパティにアクセスすると、例外が発生します。この場合、例外をスローせず、例外をスローせずにコードに値へのアクセスを許可するとしますか?意味のないゲッターから値を取得しても、コードは幸せそうに見えます。ゲッターを呼び出すコードを悪い状況に置きました。これは、値をチェックして意味がないかどうかを確認する方法を知っている必要があるためです。これは、コードがそれを検証するために、プロパティゲッターから取得した値について仮定する必要があることを意味します。これが悪いコードの書き方です。

0
Jack D Menendez