web-dev-qa-db-ja.com

フェイルファースト設計とコンストラクタロジックの制限

どのプログラミングタスクでも、私の好みはフェイルファストコードを書くことです。それはあまり物議を醸すようには思えない。ただし、多くの開発者がコンストラクターはできる限り少ないことを行うべきだと言うのも見ました。私はこれら2つの目標がしばしば矛盾していることを発見しています。フェイルファスト設計とコンストラクターの単純さを巡る会話は、優先順位の表明に発展する場合があります。

ファイルへの書き込みアクセスを提供するクラスを考えます。クラスは、既存のファイルへのパスである必要があるコンストラクター内のファイルパスを受け入れます。一部の開発者は、コンストラクターがファイルシステムにアクセスするべきではないと言うので、私はnullチェックを行うだけかもしれません。

private readonly string file;

public FileAccessProvider(string file)
{
    this.file = string.IsNullOrEmpty(file)
        ? throw new ArgumentException(nameof(file)) : file;
}

ここで、提供されたファイルが存在しない場合、クラスが実際にファイルにデータを書き込もうとするまで、そのことを知ることができない可能性があります。この場合、コンストラクターにファイルが存在するかどうかを確認しても問題ありませんか?

private readonly string file;

public FileAccessProvider(string file)
{
    this.file = string.IsNullOrEmpty(file)
        ? throw new ArgumentException(nameof(file)) : file;

    if (!File.Exists(file))
    {
        throw new ArgumentException("File does not exist.");
    }
}

これは私のコンストラクターを複雑にしますが、フェイルファスト設計に忠実に従います。これをさらに一歩進め、ユーザーがファイルへの書き込み権限を持っているかどうかをチェックするコードをコンストラクターに追加することもできます。

データベースまたはネットワークリソースにアクセスする必要があるクラスはどうですか?

このような状況でエラーが発生することは認められていますか?

5
Alan Shearer

実際のファイルI/Oの例から少し離れてみましょう。ご承知のとおり、これは、ここでは質問するつもりのなかった質問の側面を紹介しています。

あなたの一般的な質問への私の答えは次のとおりです: 「制限的なコンストラクタロジック」という考えをそのままにしないでください!

オブジェクトのコンストラクターがオブジェクトを適切に初期化できない場合、例外をスローすることは、これを呼び出し元に通知する標準的な方法(場合によっては唯一の合理的な方法)です。そして、初期化が成功したかどうかの決定で、コンストラクタ内で多少複雑な検証ロジックを実行する必要がある場合、少なくともほとんどの場合、それは問題ありません。これは通常、コンストラクター内で受け入れられる一種の「ロジック」です。

もちろん、オブジェクトの2段階の初期化が望ましい複雑なケースもあります。その場合、コンストラクターは基本的な初期化のみを行い、「重いリフティング」には別個のInitメソッドが必要です。しかし、そのような理由は、設計が「コンストラクター内で動作しない」のような頭の悪いルールであってはならないということです。このような設計の賢明な理由は、たとえば、ファクトリで基本的なオブジェクト構築を行う必要があり、Initメソッドが テンプレートの一部として呼び出される仮想メソッドである場合などです。方法

参照: コンストラクタが例外をスローするのが適切な場合は?

8
Doc Brown

私は 失敗する 自分を深く信じています。システムの整合性を促進することに加えて、開発者が問題の原因にできるだけ近づくようにすることで、開発者のデバッグに役立ちます。これにより、悪い値がどこから来たのかを解明するためにファイルごとにログに記録する必要がなくなります。

ただし、ファイルI/Oは、制御するふりをする必要があるものではありません。あなたはしません。あなたは行くつもりはありません。ファイルが存在することをテストしてから、それを使用してみます。それでは Murphy は、コードが重要なシステムにデプロイされて、テストしたときと使用しようとしたときにファイルを削除、名前変更、または移動するまで待機します。

ファイルを使用するたびに、そのファイルが存在しない可能性があり、その準備ができていると思われます。事前にあなたがすることはこれを免れません。それは単にI/Oが機能する現実です。マーフィーがこれを行わないふりをしないでください。あなたのアプリがシステム上で唯一のものだからです。マーフィーがしなければならない場合、彼はあなたのデータケーブルをかじるネズミの形を取るでしょう。

さらに、ファイルが存在しない場合にエラーをスローするのはなぜですか?たぶん、これはユーザーを煩わせることなくファイルを存在させるチャンスです。フェイルファストは、悲観的であるという意味ではありません。

システムがc:\MS\Windowsおよびユーザーが/dev/nulあなたはナイスで、彼らの入力を解析して、それが使用できないことに気付くでしょう。これは検証です。これは、インスタンス変数を設定する以外に、コンストラクタが行うべきことの1つです。ファイルシステムの現在の状態についてではありません。それはあなたの入力ががらくたであるかどうかについてです。

検証には、周辺機器との会話は含まれません。実際、検証しているものは完全に不変でなければならず、そうでなければテストは時間の無駄です。

Fail fastはこれを覆すことはありません。 Fail fastは、いったんファイルを使用しようとするとエラーが発生したため、その例外は後でなく、すぐに取得する必要があると主張しています。しかし、以前にもそうではありませんでした。

11
candied_orange

ここには2つの非常に異なるケースがあると思います。

  • ヌルチェック。これはプログラムのバグであるため、できるだけ早くスローする必要があります。その文字列がnull以外になることはなく、回復または変更する方法はありません。これは間違いであり、すぐにキャッチする必要があります。ファイルがnullの場合、この壊れたクラスを続行しても意味がありません。すべてがコールチェーンのずっと先にある理由を理解する必要がある前に、何かが間違っていることをすぐに知らせてください。

  • 他の外部依存関係のチェック。それらは無意味なので、とにかく良いチェックではありません。コンストラクター内のファイルの状態は、実際のメソッドの呼び出し時の状態については何も言いません。ファイルが存在し、書き込みアクセス権があるとします。これは、ファイル書き込みメソッドを実際に呼び出すときには変更されている可能性があります。したがって、チェックは無意味で不必要です。とにかく、実際のメソッドの例外を処理する必要があるため、2か所で処理させないでください。

このパターンは.NETで広く使用されています。たとえば、C#のすべてのファイル処理メソッドは、入力データがnullになるとすぐにArgumentNullExceptionをスローしますが、書き込みアクセス権があるかどうかを簡単に確認するメソッドはありません。事前に確認しても意味がありません。実際にファイルに書き込もうとすると表示されます。あなたが知っている持っていた書き込みアクセス2分前はまったく役に立ちません。

1
nvoigt

ソフトウェア設計の原則としてfail fastについて聞いたことがありません。私はfail fastをプロジェクトまたは組織の戦略として見ています。プロジェクトの失敗をできるだけ早く知りたいので、コースを制限しながら修正または中止することができます。間違った方向に進んだコスト。未知数が多い場合のリスク軽減戦略です。

また、できる限り遅く、つまり必要なときに仕事をするという目標も提案します。あなたの例では、ファイルにアクセスするメソッドが呼び出されるまで、ファイルが存在するかどうか、または名前がもっともらしいかどうかはチェックしません。実際の例(クラスが複数の種類の作業を行う場合)では、その時間は決して到着しない場合があります。

多分それはあなたの例の性質が単純化されているためでしょう-私はこれらのチェックのどれもしません。 framework は、それ自体でArgumentNullExceptionまたはFileNotFoundException(またはSecurityException)をスローします。

構造化例外処理の利点は、全体にわたってハッピーパスに焦点を当てることができ、すべてのレイヤーにエラーチェックがなくてもエラーメッセージをスタックに渡すことができることです。

0
Martin K

プロバイダークラスは、提供とエラーチェックを実際に行うメソッドを公開する必要があるようです。

そうすれば、クラスとコンストラクターはナイスでシンプルになり(または必要な他の依存関係を取り込むだけ)、副作用はなくなります。これにより、コンストラクターのオーバーロードやその他のリファクタリングを行わなくても、クラスに他のメソッドを追加して、さまざまなデータ型や機能をサポートできます。

このプロバイダーをインスタンス化する必要がある場合、メソッドを介して何かを実行するように指示するまで、論理的な選択を開始することは期待できません。

おそらくより良いでしょう:

public class FileAccessProvider
{
    public string Get(string file)
    {
        this.file = string.IsNullOrEmpty(file) ? throw new ArgumentException(nameof(file)) : file;

        if (!File.Exists(file))
        {
            throw new ArgumentException("File does not exist.");
        }

        //get object

        return object;
    }
}
0
NKCampbell

この場合、コンストラクターにファイルが存在するかどうかを確認しても問題ありませんか?

番号。

率直に言って、コンストラクターでnullチェックを行うことは受け入れられません。

(なぜですか?コンストラクターからスローしたときの動作を知っている人がほとんどいないためです。オブジェクトは作成されますか?それを安全に使用できますか?コンストラクターが型階層の真ん中にある場合はどうですか?)

さらに悪いことに、このチェックは(権限チェックとともに)効果がありません。ファイルが存在するnowであり、権限があるnowであっても、それを使用しようとしたときに適切な権限でファイルが存在するという意味ではありません(逆も同様です)。

このような状況でエラーが発生することは認められていますか?

C#および類似の言語では、単純なオブジェクトの作成が失敗する可能性がある場合、大まかなチェック(パスがnull以外)を実行して新しいオブジェクトを返す静的関数を作成します。そうすれば、確実に速く失敗するのに十分なロジックが得られますwillは実際に失敗します。

0
Telastyn

パーティーに遅れますが、コンストラクターをシンプルに保ち、オブジェクトの作成にロジックを適用できるようにする1つのオプションは、 静的ファクトリーメソッド を使用することです。

コンストラクターを非表示にすることで、新しいオブジェクトを作成するための明確で名前が付けられたさまざまな方法を提供できます。実行する必要のある検証や計算は、ファクトリメソッドとコンストラクタの本体に配置されます。

これは、不変オブジェクト(デフォルトの選択であるIMO)を作成する場合に特に役立ちます。コンストラクターが単純な場合は、不変オブジェクトを作成する方が簡単な傾向があります。このアプローチを使用して、コンストラクターの後に値を設定する「init」メソッドの必要性を排除できたことがわかりました。

0
JimmyJames