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


110

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




1
私は両方の質問を読みましたが、どちらもこの質問には完全にIMOで答えることはできませんでした。
Jon Seigel、

必要なときはいつでも。これまでの質問と回答はどちらも、getterまたはsetterから例外を発生させることが許可されていることを示しているため、簡単に「賢く」することができます。
Lex Li

回答:


134

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

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

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

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

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

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


41
「メモリ不足」などの致命的な例外が発生している場合、プロパティで例外を取得するか、他の場所で取得するかはほとんど問題になりません。プロパティで取得しなかった場合は、メモリを割り当てる次のものから数ナノ秒後に取得します。問題は、「プロパティが例外をスローできるか」ではありません。致命的な状態が原因で、ほとんどすべてのコードが例外をスローできます。質問は、プロパティが必要があるかどうかである設計により、その特定の契約の一部として例外をスローします。
Eric Lippert

1
私はこの答えの議論を理解しているとは思いません。xampleの場合、データバインディングについて-WinFormsとWPFの両方は、プロパティによってスローされた例外を適切に処理し、検証エラーとして扱うように特別に記述されています。 。
Pavel Minaev 2009

6
@Pavel-WinFormsとWPFはどちらもプロパティアクセサーの例外から正常に回復できますが、そのようなエラーを特定して回復することは必ずしも簡単ではありません。場合によっては(WPFでコントロールテンプレートセッターが例外をスローする場合など)、例外が警告なしに飲み込まれます。これまでにそのようなケースを実行したことがない場合、これはデバッグセッションに苦痛をもたらす可能性があります。
LBushkin 2009

1
@スティーブン:では、例外的なケースでは、そのクラスはどの程度使用されましたか?次に、1つの失敗のためにこれらすべての例外を処理する防御的なコードを記述し、おそらく適切なデフォルトを提供しなければならなかった場合、それらのデフォルトをキャッチに提供しないのはなぜですか?または、プロパティの例外がユーザーにスローされた場合、元の「InvalidArgumentException」などをスローして、不足している設定ファイルを提供できるようにしないでください。
Zhaph-Ben Duguid

6
これらがルールではなくガイドラインである理由があります。クレイジーエッジのケースをすべて網羅するガイドラインはありません。私はおそらく自分でプロパティではなくこれらのメソッドを作成したでしょうが、それは判断の呼びかけです。
エリックリッペルト

34

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

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

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


4
Pavelに感謝します。この答えは、プロパティから例外をスローするのは良い考えではないことを単に再度述べるのではなく、「なぜ」につながります。
SolutionYogi

1
の後には絶対にすべてのメンバーをIDisposable無意味にする必要があるという考えが嫌いDisposeです。メンバーを呼び出すときに使用Dispose不可になったリソースを使用する必要がある場合(たとえば、メンバーは閉じられたストリームからデータを読み取る場合)、メンバーはObjectDisposedExceptionリークするのではなくスローする必要ArgumentExceptionがありますが、特定のフィールドの値については、必要な場合よりも、廃棄後にこのようなプロパティを読み取れるようにする(最後に入力された値を生成する)方が
はるかに役立つ

1
...そのDisposeようなプロパティがすべて読み取られるまで延期されます。あるスレッドがオブジェクトのブロック読み取りを使用し、別のスレッドがそれを閉じる場合、およびの前にいつでもデータが到着する場合は、受信データDisposeDispose遮断して、以前に受信したデータを読み取ることができると便利な場合があります。他の方法では存在する必要のない状況CloseDispose状況を人為的に区別してはなりません。
スーパーキャット2012年

ルールの理由を理解することで、ルールを破るタイミングを知ることができます(Raymond Chen)。この場合、何らかのタイプの回復不可能なエラーがある場合、アプリケーションをできるだけ早く終了する必要があるため、ゲッターでそれを非表示にしないでください。
ベン

私がしようとしていた点は、プロパティゲッターには通常、回復不可能なエラーを許容するロジックを含めてはならないということです。もしそうなら、それはGet...代わりにメソッドとしてより良いことかもしれません。ここでの例外は、プロパティを提供する必要がある既存のインターフェイスを実装する必要がある場合です。
Pavel Minaev 2015

24

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

これらの種類の質問に最適なリソースは、CwalinaとAbramsによる「フレームワーク設計ガイドライン」です。製本された本として入手でき、その大部分はオンラインでも入手できます。

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

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

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


2
(一般的に)私はそのようなガイドラインに同意しますが、なぜ従うべきか、そしてそれらが無視された場合にどのような結果が生じる可能性があるかについていくつかの追加の洞察を提供することは有用だと思います。
LBushkin 2009

3
これは、使い捨てオブジェクトとObjectDisposedException、オブジェクトがDispose()呼び出されてプロパティ値を要求されたときにスローすることを検討する必要があるガイダンスとどのように関連していますか?ガイダンスは、「オブジェクトが破棄されていない限り、プロパティゲッターからの例外のスローを避けてください。その場合、ObjectDisposedExcpetionのスローを検討する必要があります」のようです。
スコットドーマン

4
デザインとは、相反する要件に直面して妥当な妥協点を見つけるための芸術と科学です。どちらの方法も合理的な妥協のようです。破棄されたオブジェクトがプロパティにスローされても驚くことはありません。そうでなかったとしても、私は驚かないでしょう。破棄されたオブジェクトを使用することはひどいプログラミング慣行であるため、期待することは賢明ではありません。
エリックリッペルト

1
ゲッター内から例外をスローすることが完全に有効なもう1つのシナリオは、オブジェクトがクラス不変式を使用して内部状態を検証する場合です。これは、メソッドやプロパティに関係なく、パブリックアクセスが行われるたびにチェックする必要があります
トラップ

2

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

例外は、例外的なプログラムの状態に対するものです。つまり、好きな場所に書いても問題ありません。

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

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

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

このルールの例外(ハハ!)はIOのようなもので、例外は制御できず、事前にチェックできません。


2
この有効で関連性のある回答はどのようにして反対投票されましたか?StackOverflowに政治はないはずであり、この答えがブルズアイを逃したと思われる場合は、その効果にコメントを追加します。反対投票は、無関係または間違っている回答に対するものです。
討論者2015年

1

これはすべて(他の回答でリンクされているように)MSDNに記載されていますが、ここでは一般的な経験則を示します...

セッターで、プロパティを型の上および上で検証する必要がある場合。たとえば、PhoneNumberと呼ばれるプロパティはおそらく正規表現の検証が必要であり、形式が無効な場合はエラーをスローする必要があります。

ゲッターの場合、値がnullの可能性がありますが、呼び出しコードで処理する必要がある可能性が最も高いです(設計ガイドラインによる)。



0

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

「遅延バインディング」とは、反射によってプロパティが見つかることを意味します。たとえば、Serializeable属性は、そのプロパティを介してオブジェクトをシリアル化/逆シリアル化するために使用されます。この種の状況で例外をスローすると、物事が壊滅的な方法で中断され、例外を使用してより堅牢なコードを作成する良い方法ではありません。

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

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


0

スローする例外がわからない場所にこのコードがありました。

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.