C#でのコンストラクターパラメーターの検証-ベストプラクティス


34

コンストラクターパラメーターの検証のベストプラクティスは何ですか?

単純なC#を想定します。

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

例外をスローすることは受け入れられますか?

私が遭遇した代替案は、インスタンス化する前の事前検証でした:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}

10
「例外をスローしても問題ありませんか?」代替手段は何ですか?
S.Lott

10
@ S.Lott:何もしません。デフォルト値を設定します。コンソール/ログファイルにメッセージを出力します。鐘を鳴らして。ライトを点滅させます。
-FrustratedWithFormsDesigner

3
@FrustratedWithFormsDesigner:「何もしない?」「空ではないテキスト」制約はどのように満たされますか?「デフォルト値を設定する」?テキスト引数値はどのように使用されますか?他のアイデアは大丈夫​​ですが、この2つはこのクラスの制約に違反する状態のオブジェクトにつながります。
-S.Lott

1
@ S.Lott私は自分自身を繰り返すことを恐れて、他のアプローチが存在する可能性に心を開いていましたが、それは知りませんでした。私はすべてを知っているとは限らないことを確信しています。
MPelletier

3
@MPelletier、事前にパラメーターを検証すると、DRYに違反することになります。(自分自身を繰り返さないでください)この事前検証を、このクラスをインスタンス化しようとするコードにコピーする必要があります。
CaffGeek

回答:


26

私はコンストラクタですべての検証を実行する傾向があります。ほとんどの場合不変オブジェクトを作成するため、これは必須です。あなたの特定の場合、これは受け入れられると思います。

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

.NET 4を使用している場合、これを行うことができます。もちろん、これは、空白のみを含む文字列を無効とみなすかどうかによって異なります。

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");

彼の「特定のケース」がそのような特定のアドバイスを提供するのに十分に具体的かどうかはわかりません。このコンテキストで例外をスローするのが理にかなっているのか、単にオブジェクトをそのままにしておくのが理にかなっているのかはわかりません。
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner-私はあなたがこれについて私に投票したと思いますか?もちろん、私が推測していることは正しいですが、入力テキストが空の場合、この理論上のオブジェクトは無効でなければなりません。Init例外が同様に機能し、オブジェクトが常に有効な状態を維持するように強制する場合、何らかのエラーコードを受け取るために呼び出す必要が本当にありますか?
ChaosPandion

2
私は、最初のケースを2つの個別の例外に分割する傾向があります。1つはnullityをテストしてをスローしArgumentNullException、もう1つはemptyをテストしてをスローする例外ですArgumentException。例外処理で呼び出し元が必要な場合、呼び出し側がよりうるさくなります。
ジェシーC.スライサー

1
@Jesse-私はそのテクニックを使用しましたが、その全体的な有用性についてはまだ垣間見ています。
ChaosPandion

3
不変オブジェクトの場合は+1!また、可能な限りそれらを使用します(私はほとんど常に個人的に見つけます)。

22

多くの人々は、コンストラクターが例外をスローすべきでないと述べています。たとえば、このページの KyleG はまさにそれを行います。正直なところ、なぜそうなのか、私には考えられません。

C ++では、コンストラクターから例外をスローするのは悪い考えです。なぜなら、参照していない初期化されていないオブジェクトを含む割り当てられたメモリが残るからです(つまり、古典的なメモリリークです)。それはおそらくスティグマの由来です-昔ながらのC ++開発者の束がC#の学習を半分中断し、C ++から得た知識をそのまま適用しました。対照的に、Objective-Cでは、Appleは割り当てステップを初期化ステップから分離したため、その言語のコンストラクターは例外スローできます。

C#は、コンストラクターの呼び出しに失敗してもメモリをリークできません。.NETフレームワークの一部のクラスでさえ、コンストラクターで例外をスローします。


あなたはC ++のコメントでいくつかの羽をフリルにするつもりです。:)
ChaosPandion

5
ええ、C ++は素晴らしい言語ですが、私の好きなことの1つは、1つの言語をよく学び、そのように常に書く人です。C#はC ++ではありません。
アント

10
C#でアクターから例外をスローすることは依然として危険な場合があります。これは、アクターが管理対象外のリソースを割り当てたために、それらが破棄されることがないためです。そして、ファイナライザは、不完全に構築されたオブジェクトがファイナライズされるシナリオに対して防御的であるように作成する必要があります。しかし、これらのシナリオは比較的まれです。C#Dev Centerに関する今後の記事では、これらのシナリオと、それらのシナリオを防御的にコーディングする方法について説明します。詳細については、そのスペースをご覧ください。
エリックリッパー

6
え?CTORから例外をスローすることで、オブジェクトを作成することができない場合は、C ++で行うことができる唯一のことで、これは理由はありません持っているメモリリークが発生するためには、(ただし、明らかにその最大)。
jk。

@jk:うーん、あなたは正しい!:それはそうけど代替はSTLが明らかに例外をスローする代わりにした、「ゾンビ」としてフラグ悪いのオブジェクトにあるyosefk.com/c++fqa/exceptions.html#fqa-17.2 のObjective-Cのソリューションは非常に整然とです。
アリ

12

クラスをそのセマンティックの使用に関して一貫した状態にすることができない例外IFFをスローします。それ以外の場合はしないでください。オブジェクトが矛盾した状態で存在することを許可しないでください。これには、完全なコンストラクターを提供しないことも含まれます(オブジェクトが実際に完全に構​​築される前に空のコンストラクター+ initialize()を用意するなど)...

ピンチで、誰もがそれを行います。先日、狭い範囲内で非常に狭く使用されるオブジェクトに対してそれを行いました。いつか、私や他の誰かが実際にそのスリップの代価を払うでしょう。

「コンストラクタ」とは、オブジェクトを構築するためにクライアントが呼び出すものを意味することに注意してください。これは、「Constructor」という名前の実際の構成体とは別の簡単なものです。たとえば、C ++のこのようなものは、IMNSHOの原則に違反しません。

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

を作成する唯一の方法funky_objectbuild_funky、実際の「コンストラクタ」がジョブを終了しなくても、無効なオブジェクトの存在を許可しないという原則を呼び出すことです。

疑わしい利益(損失かもしれません)のために、それは多くの余分な仕事です。私はまだ例外ルートを好むでしょう。


9

この場合、ファクトリメソッドを使用します。基本的に、クラスにはプライベートコンストラクターのみを設定し、オブジェクトのインスタンスを返すファクトリメソッドを設定します。初期パラメーターが無効な場合は、nullを返し、呼び出し元のコードに処理を決定させるだけです。

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

条件が例外でない限り、例外をスローしないでください。この場合、空の値を簡単に渡すことができると考えています。その場合、このパターンを使用すると、MyClassの状態を有効に保ちながら例外とそれらが被るパフォーマンスのペナルティを回避できます。


1
利点が見当たりません。nullのファクトリの結果をテストするのは、コンストラクターから例外をキャッチできるtry-catchがある場所よりも望ましいのはなぜですか?あなたのアプローチは「例外を無視して、メソッドからの戻り値を明示的にチェックしてエラーを確認する」に戻るようです。例外は一般に、すべてのメソッドの戻り値のエラーを明示的にテストするよりも優れているため、アドバイスは疑わしいと思われました。ここで一般的なルールが当てはまらないと思う理由を正当化したいと思います。
DW

例外がリターンコードよりも優れているとは決して受け入れませんでした。スローされる回数が多すぎると、パフォーマンスに大きな打撃を与える可能性があります。ただし、管理されていないものを割り当てた場合、コンストラクターから例外をスローすると、.NETでもメモリリークが発生します。このケースを補うには、ファイナライザーで多くの作業を行う必要があります。とにかく、ここではファクトリーをお勧めします。TBHはnullを返す代わりに例外をスローする必要があります。少数の「不良」クラスのみを作成すると仮定すると、ファクトリーをスローすることが望ましいです。
gbjbaanb

2
  • コンストラクターには副作用がありません。
    • プライベートフィールドの初期化以外のものは、副作用として見なされる必要があります。
    • 副作用のあるコンストラクターは、単一責任原則(SRP)を破り、オブジェクト指向プログラミング(OOP)の精神に反して実行されます。
  • コンストラクターは軽量で、失敗することはありません。
    • たとえば、コンストラクター内にtry-catchブロックが表示されると、私は常に身震いします。コンストラクターは、例外をスローしたり、エラーをログに記録したりしないでください。

これらのガイドラインに合理的に疑問を呈し、「しかし、私はこれらの規則に従わず、私のコードは正常に動作します!」と言うことができます。それに対して、「そうでないまで、それは本当かもしれません。」と答えます。

  • コンストラクター内の例外とエラーは非常に予期しないものです。そうするように言われない限り、将来のプログラマーは、これらのコンストラクター呼び出しを防御的なコードで囲む傾向がなくなります。
  • 本番環境で何かが失敗すると、生成されたスタックトレースの解析が困難になる場合があります。スタックトレースの最上部はコンストラクター呼び出しを指している場合がありますが、コンストラクターで多くのことが発生し、失敗した実際のLOCを指し示していない場合があります。
    • これが当てはまる場合、多くの.NETスタックトレースを解析しました。

0

MyClassの動作に依存します。MyClassが実際にデータリポジトリクラスであり、パラメータテキストが接続文字列である場合、ベストプラクティスはArgumentExceptionをスローすることです。ただし、MyClassがStringBuilderクラスの場合(たとえば)、空白のままにしておくことができます。

それは、パラメータテキストがメソッドにとってどれだけ重要であるかに依存します。オブジェクトは、nullまたは空白の値で意味をなしますか?


2
この質問は、クラスが空でない文字列を実際に必要とすることを意味すると思う。StringBuilderの例ではそうではありません。
セルヒオアコスタ

その場合、答えは「はい」でなければなりません-例外をスローすることは許容されます。このエラーを試行/キャッチする必要を回避するために、ファクトリパターンを使用してオブジェクトの作成を処理できます。これには空の文字列のケースが含まれます。
スティーブンストリガ

0

私の好みはデフォルトを設定することですが、Javaにはこのようなことをする「Apache Commons」ライブラリがあり、それもかなり良い考えだと思います。無効な値によってオブジェクトが使用不可能な状態になる場合、例外をスローする問題は見当たりません。文字列は良い例ではありませんが、もしそれが貧乏人のDIの場合はどうでしょうか?インターフェイスnullの代わりにの値が渡された場合、操作できませんでしたICustomerRepository。このような状況では、例外をスローすることが物事を処理する正しい方法です。


-1

私がc ++で作業していたとき、メモリリークの問題が発生する可能性があるため、コンストラクタで例外をスローすることはしませんでした。C ++を使用している人は誰でも、メモリリークがどれほど困難で問題があるかを知っています。

ただし、c#/ Javaを使用している場合は、ガベージコレクターがメモリを収集するため、この問題は発生しません。C#を使用している場合、データ制約が保証されていることを確認するために、プロパティを使用することをお勧めします。

public class MyClass
{
    private string _text;
    public string Text 
    {
        get
        {
            return _text;
        } 
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Text cannot be empty");
            _text = value;
        } 
    }

    public MyClass(string text)
    {
        Text = text;
        // continue with normal construction
    }
}

-3

例外をスローすることは、クラスのユーザーにとって大きな苦痛になります。検証が問題である場合、通常、オブジェクトの作成を2ステップのプロセスにします。

1-インスタンスを作成します

2-戻り結果でInitializeメソッドを呼び出します。

または

検証を実行できるクラスを作成するメソッド/関数を呼び出します。

または

isValidフラグがあります。これは特に好きではありませんが、一部の人は気に入っています。


3
@Dunk、それは間違っています。
CaffGeek

4
@Chad、それはあなたの意見ですが、私の意見は間違っていません。それはあなたのものとはまったく異なります。try-catchコードは読むのがはるかに難しく、従来の方法よりも些細なエラー処理にtry-catchを使用すると、はるかに多くのエラーが発生します。それは私の経験であり、間違いではありません。それが現実さ。
ダンク

5
POINTは、コンストラクターを呼び出した後、入力が無効であるために作成されない場合、それは例外です。オブジェクトを作成し、フラグを設定するだけの場合、アプリのデータは一貫性のない/無効な状態になりますisValid = false。クラスが有効である場合、クラスが作成された後にテストすることは恐ろしく、非常にエラーが発生しやすい設計です。そして、あなたは言った、コンストラクタを持っているし、初期化を呼び出す...私が初期化を呼び出さないとどうなりますか?じゃあ そして、Initializeが不正なデータを取得した場合、今すぐ例外をスローできますか?クラスの使用は難しくないはずです。
CaffGeek

5
@Dunk、例外を使用するのが難しいと思うなら、あなたはそれらを間違って使用しています。簡単に言えば、悪い入力がコンストラクタに提供されました。それは例外です。修正しない限り、アプリの実行を継続しないでください。期間。もしそうなら、あなたはより悪い問題、悪いデータで終わります。例外の処理方法がわからない場合は、それが処理されるまで呼び出しスタックをバブルアップさせてください。簡単に言えば、例外を処理したり、例外をテストしたりする必要はありません。彼らは単に動作します。
CaffGeek

3
申し訳ありませんが、これは考慮すべきアンチパターンです。
MPelletier
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.