引数を検証するコンストラクターはSRPに違反しますか?


66

私は可能な限り単一責任原則(SRP)に準拠しようとしていますが、デリゲートに大きく依存する特定のパターン(メソッドのSRP)に慣れました。このアプローチが適切かどうか、または深刻な問題があるかどうかを知りたいです。

たとえば、コンストラクターへの入力を確認するために、次のメソッドを導入できます(Stream入力はランダムで、何でも可能です)

private void CheckInput(Stream stream)
{
    if(stream == null)
    {
        throw new ArgumentNullException();
    }

    if(!stream.CanWrite)
    {
        throw new ArgumentException();
    }
}

このメソッドは(おそらく)複数のことを行います

  • 入力を確認する
  • さまざまな例外をスローする

したがって、SRPを順守するために、ロジックを

private void CheckInput(Stream stream, 
                        params (Predicate<Stream> predicate, Action action)[] inputCheckers)
{
    foreach(var inputChecker in inputCheckers)
    {
        if(inputChecker.predicate(stream))
        {
            inputChecker.action();
        }
    }
}

たぶん1つのことだけを行う(行うのか?):入力を確認する。入力の実際のチェックと例外のスローのために、次のようなメソッドを導入しました

bool StreamIsNull(Stream s)
{
    return s == null;
}

bool StreamIsReadonly(Stream s)
{
    return !s.CanWrite;
}

void Throw<TException>() where TException : Exception, new()
{
    throw new TException();
}

CheckInputように呼び出すことができます

CheckInput(stream,
    (this.StreamIsNull, this.Throw<ArgumentNullException>),
    (this.StreamIsReadonly, this.Throw<ArgumentException>))

これは最初のオプションよりも優れていますか、それとも不要な複雑さを導入しますか?このパターンを実行可能な場合でも、このパターンを改善できる方法はありますか?


26
私はそれが主張する可能性がCheckInputまだ複数の事をやっている:これは、両方の配列を反復処理である述語関数を呼び出す、アクション関数を呼び出します。それはSRPの違反ではありませんか?
バートヴァンインゲンシェナウ

8
はい、それは私が作ろうとしていたポイントです。
バートヴァンインゲンシェナウ

135
それが単一の責任原則であることを覚えておくことは重要です。シングルアクションの原則ではありません。1つの責任があります:ストリームが定義され、書き込み可能であることを確認します。
デビッドアルノ

40
これらのソフトウェアの原則のポイントは、コードをより読みやすく保守しやすくすることです。元のCheckInputは、リファクタリングされたバージョンよりも読みやすく、保守しやすいです。実際、コードベースで最後のCheckInputメソッドに出くわした場合は、すべてを破棄し、元の内容に一致するように書き換えます。
17年

17
これらの「原則」は実際には役に立たない。なぜなら、元のアイデアが何であったとしても、どの方法でも「単一責任」を定義することができるからだ。しかし、それらを厳密に適用しようとすると、率直に言って理解しにくいこの種のコードになってしまうと思います。
ケーシー

回答:


151

SRPはおそらく最も誤解されているソフトウェア原則です。

ソフトウェアアプリケーションは、モジュールから構築されます。モジュールは、モジュールから構築されます。

一番下のような単一の関数にCheckInputはほんの少しのロジックしか含まれていませんが、上に行くにつれて、連続する各モジュールはより多くのロジックカプセル化し、これは正常です。

SRPは、単一のアトミックアクションを実行することではありません。複数のアクションが必要な場合でも、単一の責任を持つことです...そして最終的には、メンテナンステスト容易性についてです:

  • カプセル化を促進し(神のオブジェクトを回避)、
  • 懸念の分離を促進します(コードベース全体で波打つ変更を回避します)。
  • 責任範囲を狭めることにより、テスト容易性に役立ちます。

CheckInput2つのチェックで実装され、2つの異なる例外を発生させるという事実は、ある程度無関係です。

CheckInput責任が狭い:入力が要件に準拠していることを確認する。はい、複数の要件がありますが、これは複数の責任があるという意味ではありません。はい、チェックを分割できますが、どのように役立ちますか?ある時点で、チェックを何らかの方法でリストする必要があります。

比較してみましょう:

Constructor(Stream stream) {
    CheckInput(stream);
    // ...
}

対:

Constructor(Stream stream) {
    CheckInput(stream,
        (this.StreamIsNull, this.Throw<ArgumentNullException>),
        (this.StreamIsReadonly, this.Throw<ArgumentException>));
    // ...
}

今、CheckInput少ない...しかし、その呼び出し元はもっとします!

要件のリストを、要件CheckInputがカプセル化されているConstructor場所から表示される場所に移動しました。

それは良い変化ですか?場合によります:

  • 場合CheckInputのみが呼び出されます。それは、コードをクラッタ一方で、それは要件が表示されるようになり、一方では、議論の余地があります。
  • 場合はCheckInput複数回呼び出される同じ要件で、それはDRY違反すると、あなたは、カプセル化の問題を持っています。

単一の責任が多くの作業を暗示している可能性があることを認識することが重要です。自動運転車の「頭脳」には、単一の責任があります。

車を目的地まで運転します。

これは単一の責任ですが、大量のセンサーとアクターを調整し、多くの決定を下す必要があり、要件が矛盾する可能性さえあります1 ...

...ただし、すべてカプセル化されています。したがって、クライアントは気にしません。

1 乗客の安全、他者の安全、規制の尊重、...


2
「カプセル化」という言葉とその派生語の使い方はわかりにくいと思います。それ以外は素晴らしい答えです!
ファビオは、モニカを復活させる

4
私はあなたの答えに同意しますが、自動運転車の脳の議論はしばしば人々をSRPを破るように誘います。あなたが言ったように、それはモジュールで作られたモジュールで作られたモジュールです。そのシステム全体の目的を特定することはできますが、そのシステム自己破壊する必要があります。ほとんどすべての問題を分解できます。
サヴァB.17年

13
@SavaB:もちろんですが、原則は変わりません。モジュールは、その構成要素よりも範囲が広いものの、単一の責任を負う必要があります。
マチューM.17年

3
@ user949300さて、「運転」だけはどうですか。実際、「運転」が責任であり、「安全に」および「合法的に」それがその責任を果たす方法に関する要件です。そして、責任を述べる際に要件をリストすることがよくあります。
ブライアンマックラッチン

1
「SRPはおそらく最も誤解されているソフトウェア原則です。」この回答から明らかなように:)
マイケル

41

SRPについてボブおじさんを引用(https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html):

Single Responsibility Principle(SRP)には、各ソフトウェアモジュールに変更すべき理由が1つだけあることが記載されています。

...この原則は人々に関するものです。

...ソフトウェアモジュールを作成するとき、変更が要求されたときに、それらの変更は単一の人、または厳密に定義された単一のビジネス機能を表す単一の密結合グループからのみ発生するようにします。

...これが、JSPにSQLを配置しない理由です。これが、結果を計算するモジュールでHTMLを生成しない理由です。これが、ビジネスルールがデータベーススキーマを認識してはならない理由です。これが、懸念を分離する理由です。

彼は、ソフトウェアモジュールが特定の利害関係者の懸念に対処する必要があると説明します。したがって、あなたの質問に答えます:

これは最初のオプションよりも優れていますか、それとも不要な複雑さを導入しますか?このパターンを実行可能な場合でも、このパターンを改善できる方法はありますか?

IMO、あなたはより高いレベル(この場合はクラスレベル)を見る必要があるときに、1つのメソッドだけを見ています。おそらく、クラスが現在何をしているのかを調べる必要があります(これには、シナリオに関する詳細な説明が必要です)。今のところ、あなたのクラスはまだ同じことをしています。たとえば、明日、検証に関する変更要求がある場合(たとえば、「ストリームがnullになる可能性がある」)、このクラスに移動して、その中のものを変更する必要があります。


4
ベストアンサー。OPについて詳しく説明すると、ガードチェックが2つの異なる利害関係者/部門から来ている場合、checkInputs()とに分割する必要がcheckMarketingInputs()ありcheckRegulatoryInputs()ます。それ以外の場合は、それらすべてを1つのメソッドに結合しても構いません。
user949300

36

いいえ、この変更はSRPから通知されません。

「渡されたオブジェクトはストリームです」という理由で、チェッカーにチェックがない理由を自問してください。答えは明白です。言語は、呼び出し元が非ストリームを渡すプログラムコンパイルするのを防ぎます。

C#の型システムは、ニーズを満たすには不十分です。あなたのチェックは今日の型システムで表現できない不変条件の実施を実装しています。メソッドがnull不可の書き込み可能なストリームを取得すると言う方法があれば、それを書いたはずですが、そうではないので、次善策を実行しました。実行時に型制限を実施しました。メソッドを使用する開発者がこれに違反してテストケースに失敗してから問題を修正する必要がないように、ドキュメントも作成してください。

メソッドに型を置くことは、単一責任原則の違反ではありません。また、メソッドは前提条件を強制したり、事後条件をアサートしたりしません。


1
また、作成されたオブジェクトを有効な状態のままにすることは、コンストラクターが基本的に常に1つの責任です。あなたが述べたように、ランタイムやコンパイラが提供できない追加のチェックが必要な場合、実際には回避方法はありません。
SBI

23

すべての責任が平等に作成されるわけではありません。

ここに画像の説明を入力してください

ここに画像の説明を入力してください

2つの引き出しがあります。どちらにも1つの責任があります。それぞれに名前があり、それらに属するものを知らせます。1つは銀製の引き出しです。もう1つはジャンクドロワーです。

それで、違いは何ですか?銀製の引き出しには、その中にないものが明確になっています。ただし、ジャンクドロワーは適合するものなら何でも受け入れます。銀器の引き出しからスプーンを取り出すのは非常に間違っているようです。しかし、ジャンクドロワーから取り外された場合に見落とされるものを考えるのは困難です。真実は、何でも単一の責任があると主張できるが、どちらに焦点を絞った単一の責任があると思いますか?

単一の責任を持つオブジェクトは、ここで1つのことだけが起こることを意味しません。責任はネストできます。しかし、これらのネストの責任は理にかなっているはずです。ここでそれらを見つけても驚くことはないはずです。

だからあなたが提供するとき

CheckInput(Stream stream);

入力のチェックと例外のスローの両方を心配しているとは思いません。入力の確認と入力の保存の両方を行っているかどうかも心配です。それは厄介な驚きです。それがなくなっても見逃さないだろう。


21

重要なソフトウェアの原則に準拠するために結び目を付けて奇妙なコードを書くとき、通常は原則を誤解しています(ただし、原則が間違っていることもあります)。Matthieuの優れた回答が指摘しているように、SRPの全体的な意味は「責任」の定義に依存しています。

経験豊富なプログラマーはこれらの原則を見て、私たちが台無しにしたコードの記憶にそれらを関連付けます。経験の浅いプログラマーはそれらを参照し、それらをまったく関連付ける必要がないかもしれません。それは空間に浮かぶ抽象化であり、すべてにやにやにやにやにや笑う。それで彼らは推測します、そして、それは通常ひどく行きます。プログラミングの馬の感覚を開発する前に、奇妙な過度に複雑なコードと通常のコードの違いはまったく明らかではありません。

これは、個人的な結果に関係なく従わなければならない宗教的な戒めではありません。これは、プログラミングの馬の感覚の1つの要素を形式化することを意図した経験則であり、コードをできるだけシンプルかつ明確に保つのに役立ちます。逆の効果がある場合は、外部入力を探すのが正しいでしょう。

プログラミングでは、見つめるだけで第一原理から識別子の意味を推測しようとするよりもはるかに間違った方向に進むことはできません。プログラミングについては、実際のコードの識別子と同じくらい、識別子について書かれています。


14

CheckInputロール

まず、私は、そこに明白なことを入れてみましょうCheckInput され、それはさまざまな側面をチェックしている場合でも、一つのことをやって。最終的に入力をチェックします。呼び出されたメソッドを処理している場合、それは1つのことではないと主張することもできますがDoSomething、入力のチェックがあまり曖昧ではないと仮定するのは安全だと思います。

述語にこのパターンを追加することは、入力をチェックするロジックをクラスに配置したくない場合に役立ちますが、このパターンは達成しようとしているものに対してかなり冗長に見えます。取得したい場合はIStreamValidator、単一のメソッドでインターフェイスを渡すだけの方がはるかに直接的なisValid(Stream)場合があります。実装IStreamValidatorするクラスはStreamIsNullStreamIsReadonly必要に応じて、または必要に応じて述語を使用できますが、中心点に戻ると、単一の責任原則を維持するために行うのはかなりばかげた変更です。

サニティーチェック

それは、我々はすべてのあなたは、少なくとも非NULLかつ書き込み可能であるストリームを扱っていることを確認するために、「健全性チェック」を許可している私の考えであり、この基本的なチェックは何とか自分のクラスになっていませんバリデータストリームのを。念のために、より洗練されたチェックはクラスの外に残すのが最善ですが、そこに線が引かれます。ストリームの読み取りまたは検証専用のリソースを使用してストリームの状態の変更を開始する必要がある場合、ストリームの正式な検証の実行を開始し、これを独自のクラスに取り込む必要があります。

結論

私の考えは、クラスの側面をより良く整理するためにパターンを適用している場合、独自のクラスにいることは価値があるということです。パターンは適合しないため、そもそもパターンが実際に独自のクラスに属しているかどうかも質問する必要があります。私の考えでは、ストリームの検証が将来変更される可能性が高いと思わない限り、特にこの検証が本質的に動的である可能性が高いと思われる場合は、記述したパターンは良いアイデアです。最初は些細なことです。それ以外の場合は、プログラムをarbitrarily意的に複雑にする必要はありません。スペードをスペードと呼びましょう。検証は1つのことですが、null入力のチェックは検証ではないため、単一の責任原則に違反することなくクラス内で安全に保持できると思います。


4

この原則では、「1つのことだけを行う」必要があるコードは明記されていません。

SRPの「責任」は要件レベルで理解する必要があります。コードの責任は、ビジネス要件を満たすことです。オブジェクトが複数の独立したビジネス要件を満たす場合、SRPに違反します。独立とは、1つの要件が変更され、他の要件がそのまま維持されることを意味します。

新しいビジネス要件が導入され、この特定のオブジェクト読み取り可能かどうかをチェックするべきではないことを意味しますが、別のビジネス要件ではオブジェクトが読み取り可能かどうかを確認する必要がありますか?いいえ。ビジネス要件では、そのレベルで実装の詳細が指定されていないためです。

SRP違反の実際の例は、次のようなコードです。

var message = "Your package will arrive before " + DateTime.Now.AddDays(14);

このコードは非常に単純ですが、テキストはビジネスのさまざまな部分によって決定されるため、テキストは予定納期とは無関係に変化すると考えられます。


事実上すべての要件に対応する異なるクラスは、不浄な悪夢のように聞こえます。
whatsisname

@whatsisname:おそらくSRPはあなたには向いていないでしょう。すべての種類とサイズのプロジェクトに適用される設計原則はありません。(ただし、独立した要件(つまり、独立して変更できる)についてのみ話しているので、それ以降の要件だけでなく、指定されたきめ細かさに依存することに
注意してください

私は、SRPが状況判断の要素を必要としているのではなく、1つのキャッチフレーズで説明するのが難しいと思います。
-whatsisname

@whatsisname:私は完全に同意します。
ジャックB

オブジェクトが複数の独立したビジネス要件を満たす場合、SRPの
Juzerアリ

3

私は@EricLippertの答えからポイントが好きです:

渡されたオブジェクトがストリームであるためチェッカーにチェックがない理由を自問してください。答えは明白です。言語は、呼び出し元が非ストリームを渡すプログラムコンパイルするのを防ぎます。

C#の型システムは、ニーズを満たすには不十分です。あなたのチェックは今日の型システムで表現できない不変条件の実施を実装しています。メソッドがnull不可の書き込み可能なストリームを取得するという方法があれば、それを書いたはずですが、そうではないので、次の最善策を実行しました。実行時に型制限を実施しました。メソッドを使用する開発者がこれに違反してテストケースに失敗してから問題を修正する必要がないように、ドキュメントも作成してください。

EricLippertは、これが型システムの問題であることを認めています。そして、単一責任原則(SRP)を使用したいので、基本的にはこの仕事を担当する型システムが必要です。

実際に、C#でこれを行うことは可能です。私たちは、リテラルキャッチすることができますnull「は、非リテラルキャッチし、コンパイル時にSをnull実行時にs」をします。これは、コンパイル時の完全なチェックほどではありませんが、コンパイル時にキャッチしないことに対する厳密な改善です。

だから、yaはC#がどのように持っているNullable<T>か知っていますか?それを逆にして、作成してみましょうNonNullable<T>

public struct NonNullable<T> where T : class
{
    public T Value { get; private set; }
    public NonNullable(T value)
    {
        if (value == null) { throw new NullArgumentException(); }
        this.Value = value;
    }
    //  Ease-of-use:
    public static implicit operator T(NonNullable<T> value) { return value.Value; }
    public static implicit operator NonNullable<T>(T value) { return new NonNullable<T>(value); }

    //  Hack-ish overloads that prevent null-literals from being implicitly converted into NonNullable<T>'s.
    public static implicit operator NonNullable<T>(Tuple<T> value) { return new NonNullable<T>(value.Item1); }
    public static implicit operator NonNullable<T>(Tuple<T, T> value) { return new NonNullable<T>(value.Item1); }
}

今、書く代わりに

public void Foo(Stream stream)
{
  if (stream == null) { throw new NullArgumentException(); }

  // ...method code...
}

、 書くだけ:

public void Foo(NonNullable<Stream> stream)
{
  // ...method code...
}

次に、3つのユースケースがあります。

  1. Foo()null以外のユーザー呼び出しStream

    Stream stream = new Stream();
    Foo(stream);

    これは望ましいユースケースであり、with-or-withoutで機能しNonNullable<>ます。

  2. ユーザーはFoo()nullで呼び出しますStream

    Stream stream = null;
    Foo(stream);

    これは呼び出しエラーです。ここでNonNullable<>は、これを行うべきではないことをユーザーに通知するのに役立ちますが、実際には停止しません。いずれにせよ、これはランタイムになりNullArgumentExceptionます。

  3. ユーザーは呼び出すFoo()null

    Foo(null);

    nullは暗黙的にに変換されないNonNullable<>ため、実行前に IDEでエラーが発生します。これは、SRPがアドバイスするように、nullチェックを型システムに委任しています。

このメソッドを拡張して、引数について他のことをアサートすることもできます。あなたが書き込み可能なストリームをしたいので、たとえば、あなたが定義することができstruct WriteableStream<T> where T:Stream、両方をチェックしているnullと、stream.CanWriteコンストラクタでは。これは実行時の型チェックのままですが、次のとおりです。

  1. 型をWriteableStream修飾子で装飾し、呼び出し元に必要性を知らせます。

  2. コードの1か所でチェックを行うため、throw InvalidArgumentException毎回チェックを繰り返す必要はありません。

  3. 型チェックの義務を型システムにプッシュすることにより、SRPによりよく準拠します(汎用デコレータによって拡張されます)。


3

あなたのアプローチは現在手続き的です。Streamオブジェクトを分解して、外部から検証しています。それをしないでください-それはカプセル化を壊します。ましょうStream、独自の検証のために責任を負うこと。SRPを適用するクラスを取得するまで、SRPを適用することはできません。

Stream検証に合格した場合にのみアクションを実行するを次に示します。

class Stream
{
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }

        System.out.println("My action");
    }
}

しかし、ではSRPに違反しています!「クラスには、変更する理由が1つしかありません。」1)検証と2)実際のロジックが混在しています。変更する必要があるかもしれない2つの理由があります。

デコレータ検証することでこれを解決できます。最初Streamに、インターフェイスに変換し、それを具体的なクラスとして実装する必要があります。

interface Stream
{
    void someAction();
}

class DefaultStream implements Stream
{
    @Override
    public void someAction()
    {
        System.out.println("My action");
    }
}

これで、aをラップしStream、検証を実行しStream、アクションの実際のロジックに対して指定されたデコレーターを遅らせるデコレーターを作成できます。

class WritableStream implements Stream
{
    private final Stream stream;

    public WritableStream(final Stream stream)
    {
        this.stream = stream;
    }

    @Override
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }
        stream.someAction();
    }
}

これらを好きなように構成できるようになりました。

final Stream myStream = new WritableStream(
    new DefaultStream()
);

追加の検証が必要ですか?別のデコレータを追加します。


1

クラスの仕事は、契約満たすサービスを提供することです。クラスには常に契約があります。それを使用するための一連の要件であり、要件が満たされている場合にクラスの状態と出力について約束します。この契約は、ドキュメントおよび/またはアサーションを介して明示的または暗黙的である場合がありますが、常に存在します。

クラスのコントラクトの一部は、呼び出し元がコンストラクターにnullであってはならない引数を与えることです。契約を実装すること、発信者が契約のその部分は簡単にクラスの責任の範囲内にあると考えることができる満たしていることを確認するので、クラスの責任。

クラスが契約を実装するという考えは、Bertrand Meyerによるものです。これは、Eiffelプログラミング言語の設計者であり、契約による設計の考えです。Eiffel言語は、言語の契約部分の仕様とチェックを行います。


0

他の回答で指摘されているように、SRPはしばしば誤解されています。1つの機能のみを実行するアトミックコードを持つことではありません。オブジェクトとメソッドが1つのことだけを行い、1つのことを1つの場所でのみ行うようにすることです。

擬似コードの貧弱な例を見てみましょう。

class Math
    private int a;
    private int b;
    def constructor(int x, int y) 
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

かなり馬鹿げた例では、Math#constructorの「責任」は、数学オブジェクトを使用可能にすることです。まず入力をサニタイズし、次に値が-1でないことを確認します。

コンストラクターは1つのことだけを行うため、これは有効なSRPです。Mathオブジェクトを準備しています。ただし、メンテナンス性はあまり高くありません。DRYに違反しています。

それで別のパスを取ることができます

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        cleanX(x)
        cleanY(y)
    end
    def cleanX(int x)
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
   end
   def cleanY(int y)
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

このパスでは、DRYについて少し良くなりましたが、DRYを使用する方法はまだあります。一方、SRPは少しずれているようです。同じジョブで2つの関数があります。cleanXとcleanYの両方が入力をサニタイズします。

もう一度やりましょう

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i != null)
          return i
        else if(i < 0)
          return abs(i)
        else if (i == -1)
          throw "Some Silly Error"
        else
          return 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

ついにDRYの方が良くなり、SRPは同意しているようです。「サニタイズ」ジョブを実行する場所は1つだけです。

理論的には、コードの保守性が向上しますが、バグを修正してコードを強化する場合は、1か所で行うだけで済みます。

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i == null)
          return 0
        else if (i == -1)
          throw "Some Silly Error"
        else
          return abs(i)
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

実際のほとんどの場合、オブジェクトはより複雑になり、SRPは多数のオブジェクトに適用されます。たとえば、年齢は父、母、息子、娘に属している可能性があるため、生年月日から年齢を計算する4つのクラスを持つ代わりに、それを行うPersonクラスがあり、4つのクラスがそれを継承します。しかし、この例が説明に役立つことを願っています。SRPはアトミックアクションではなく、「ジョブ」の完了に関するものです。


-3

SRPといえば、ボブおじさんはどこにでも散らばるnullチェックが好きではありません。一般に、チームとして、可能な限りコンストラクターにnullパラメーターを使用しないでください。チームの外部でコードを公開すると、状況が変わる可能性があります。

問題のクラスの凝集性を最初に保証することなく、コンストラクターパラメーターのnull不可能性を強制すると、呼び出しコード、特にテストが肥大化します。

そのような契約を本当に強制したい場合はDebug.Assert、混乱を減らすために、または同様のものを使用することを検討してください:

public AClassThatDefinitelyNeedsAWritableStream(Stream stream)
{
   Assert.That(stream.CanWrite, "Put crucial information here, and not inane bloat.");

   // Go on normal operation.
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.