おそらく役に立たない例外処理でコードを強化する


12

コードの別の部分が正しくコーディングされていない場合に備えて、無駄な例外処理を実装することをお勧めしますか?

基本的な例

単純なものなので、私は皆を失うわけではありません:)。

データベースから抽出されたデータを人の情報(名前、住所など)を表示するアプリを書いているとしましょう。私がUI部分をコーディングしている人で、他の誰かがDBクエリコードを書いているとしましょう。

ここで、アプリの仕様で、人の情報が不完全な場合(データベースに名前がないなど)、クエリをコーディングしている人が、不足しているフィールドに「NA」を返すことでこれを処理する必要があると考えているとします。

クエリのコーディングが不十分で、このケースを処理できない場合はどうなりますか?クエリを作成した人が不完全な結果を処理し、情報を表示しようとすると、コードが空のものを表示する準備ができていないため、すべてがクラッシュするとどうなりますか?

この例は非常に基本的なものです。私はあなたのほとんどが「あなたの問題ではない、あなたはこのクラッシュの責任を負わない」と言うと信じています。ただし、クラッシュするのは依然としてコードの一部です。

もう一つの例

今、私がクエリを書いている人だとしましょう。仕様は上記と同じではありませんが、「挿入」クエリを作成する人は、データベースに人を追加するときにすべてのフィールドが完全であることを確認して、不完全な情報を挿入しないようにする必要があります。UIの人に完全な情報を提供するために、「選択」クエリを保護する必要がありますか?

質問

仕様に「この人がこの状況を処理する責任者である」と明示的に述べていない場合はどうなりますか?第三者が別のクエリ(最初のクエリに似ていますが、別のDB上にある)を実装し、UIコードを使用してそれを表示しますが、コードでこのケースを処理しない場合はどうなりますか?

私が悪いケースを処理することになっていない場合でも、起こりうるクラッシュを防ぐために必要なことをすべきですか?

私はここで衝突を解決していないので、「(彼)がクラッシュの原因である」などの答えを探していません。私の責任ではない状況からコードを保護する必要があります処理する?ここでは、単純な「空の場合は何かを行う」で十分です。

一般に、この質問は冗長な例外処理に取り組んでいます。私がそれを求めているのは、私がプロジェクトで一人で作業するとき、「万が一に備えて」何か間違ったことをして、悪いケースを通過させるために、連続する関数で同様の例外処理を2〜3回コーディングする可能性があるためです。


4
あなたは「テスト」について話していますが、私があなたの問題を理解している限り、あなたは「生産で適用されるテスト」を意味します、これは「検証」または「例外処理」と呼ばれます。
ドックブラウン

1
はい、適切な言葉は「例外処理」です。
rdurand

間違ったタグを変更した
Doc Brown

私は、あなたはを参照してくださいDailyWTF -あなたがしていることを確認しますが、テストのこの種をやってみたいですか?
gbjbaanb

@gbjbaanb:あなたのリンクを正しく理解していれば、それは私が話していることとはまったく違います。私は「愚かなテスト」の話ではなく、例外処理の複製について話しています。
rdurand

回答:


14

ここで話しているのは信頼境界です。アプリケーションとデータベースの境界を信頼していますか?データベースは、アプリケーションからのデータが常に事前検証されていることを信頼していますか?

それは、すべてのアプリケーションで行われなければならない決定であり、正解も不正解もありません。私はあまりにも多くの境界を信頼境界と呼んでいますが、他の開発者は常にサードパーティのAPIを信頼して、あなたが期待することを常に実行しています。


5

堅牢性の原則「送信するものを保守的にし、受け入れるものをリベラルにする」が、あなたが求めているものです。それは良い原則です-編集:そのアプリケーションが重大なエラーを隠さない限り-しかし、あなたがそれを適用すべきかどうかは常に状況に依存することを@pdrに同意します。


一部の人々は、「堅牢性の原則」はくだらないと考えています。この記事では例を示します。

@MattFenwick:それを指摘してくれてありがとう、その有効なポイント、私は答えを少し変えました。
ドックブラウン

2
これは、「ロバスト性の原則」の問題を指摘するさらに優れた記事です。joelonsoftware.com
2008/03

1
@hakoja:正直なところ、私はこの記事をよく知っています。それは、堅牢性の原則に従わない場合に生じる問題についてです(一部のMSの人が新しいIEバージョンで試したように)。それでも、これは元の質問から少し離れています。
ドックブラウン

1
@DocBrown:それがまさにあなたが受け入れることにおいて決して寛大であるべきではなかった理由です。堅牢性とは、投げられたすべてを文句なしに受け入れる必要があるということではなく、投げられたすべてをクラッシュせずに受け入れる必要があるということです。
マルジャンヴェネマ

1

テスト対象に依存します。しかし、テストの範囲はあなた自身のコードのみであると仮定しましょう。その場合、以下をテストする必要があります。

  • 「ハッピーケース」:アプリケーションに有効な入力を送り、正しい出力が生成されることを確認します。
  • 失敗のケース:アプリケーションに無効な入力をフィードし、それらが正しく処理されることを確認します。

これを行うには、同僚のコンポーネントを使用できません。代わりに、mockingを使用します。つまり、アプリケーションの残りの部分を、テストフレームワークから制御できる「偽の」モジュールに置き換えます。これを正確に行う方法は、モジュールのインターフェイス方法によって異なります。ハードコードされた引数を使用してモジュールのメソッドを呼び出すだけで十分であり、他のモジュールのパブリックインターフェイスをテスト環境に接続するフレームワーク全体を記述するのと同じくらい複雑になります。

ただし、これはユニットテストケースにすぎません。統合テストも必要です。統合テストでは、すべてのモジュールをまとめてテストします。繰り返しますが、幸せなケースと失敗の両方をテストする必要があります。

「基本的な例」の場合、コードを単体テストするには、データベース層をシミュレートする模擬クラスを作成します。ただし、モッククラスは実際にはデータベースに移動しません。予想される入力と固定出力をプリロードするだけです。擬似コードで:

function test_ValidUser() {
    // set up mocking and fixtures
    userid = 23;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "Doe" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);
    expectedResult = "John Doe";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

そして、正しく報告される欠落フィールドをテストする方法は次のとおりです。

function test_IncompleteUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "NA" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // let's say the user controller is specified to leave "NA" fields 
    // blank
    expectedResult = "John";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

物事が面白くなりました。実際のDBクラスが正しく動作しない場合はどうなりますか?たとえば、不明な理由で例外をスローする可能性があります。実行されるかどうかはわかりませんが、独自のコードで適切に処理する必要があります。問題ありません。MockDBに例外をスローさせるだけです。たとえば、次のようなメソッドを追加します。

class MockDB {
    // ... snip
    function getUser(userid) {
        if (this.fixedException) {
            throw this.fixedException;
        }
        else {
            return this.fixedResult;
        }
    }
}

そして、テストケースは次のようになります。

function test_MisbehavingUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedException = new SQLException("You have an error in your SQL syntax");
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // run the actual test
    try {
        userController.displayUserAsString(userid);
    }
    catch (DatabaseException ex) {
        // This is good: our userController has caught the raw exception
        // from the database layer and wrapped it in a DatabaseException.
        return TEST_PASSED;
    }
    catch (Exception ex) {
        // This is not good: we have an exception, but it's the wrong kind.
        testLog.log("Found the wrong exception: " + ex);
        return TEST_FAILED;
    }
    // This is bad, too: either our mocking class didn't throw even when it
    // should have, or our userController swallowed the exception and
    // discarded it
    testLog.log("Expected an exception to be thrown, but nothing happened.");
    return TEST_FAILED;
}

これらはユニットテストです。統合テストでは、MockDBクラスを使用しません。代わりに、両方の実際のクラスを連結します。まだフィクスチャが必要です。たとえば、テストを実行する前に、テストデータベースを既知の状態に初期化する必要があります。

さて、責任に関する限り、コードはコードベースの残りが仕様ごとに実装されることを期待する必要がありますが、残りが台無しになったときに適切に処理できるように準備する必要があります。自分以外のコードをテストするための責任を負いませんが、あなたはしているもう一方の端でコードを不正な動作へのあなたのコードは、弾性作るための責任、そしてまた、あなたのコードの回復力をテストするための責任があります。これが、上記の3番目のテストで行われます。


質問の下のコメントを読みましたか?OPは、「テスト」を書いたが、彼は、「妥当性チェック」および/または「例外処理」の意味で、それが意味
ドク・ブラウン

1
@tdammers:誤解して申し訳ありませんが、実際には例外処理を意味していました。
rdurand

1

私がコーディングしようとしている主な原則は3つあります。

  • ドライ

  • 接吻

  • ヤグニ

これらすべてをこすり落とすのは、他の場所で複製された検証コードを書く危険があるということです。検証ルールが変更された場合、これらを複数の場所で更新する必要があります。

もちろん、将来のある時点で、データベースを再プラットフォーム化する可能性があります(それが起こる場合)。しかし...あなたは起こらないかもしれない何かのためにコーディングしています。

追加のコード(変更されない場合でも)は、書き込み、読み取り、保存、およびテストが必要になるため、オーバーヘッドになります。

上記のすべてが当てはまる場合、検証をまったく行わないのはあなたの怠慢です。アプリケーションでフルネームを表示するには、データ自体を検証しなくても、いくつかの基本データが必要です。


1

素人の言葉で。

「データベース」「アプリケーション」のようなものはありません。

  1. データベースは、複数のアプリケーションで使用できます。
  2. アプリケーションは複数のデータベースを使用できます。
  3. データベースモデルでは、データ定義を強制する必要があります。これには、テーブル定義でデフォルト値が定義されていない限り、必須フィールドが挿入操作に含まれていない場合にエラーをスローすることが含まれます。これは、アプリをバイパスしてデータベースに行を直接挿入する場合でも実行する必要があります。データベースシステムに任せてください。
  4. データベースはデータの整合性を保護し、エラースローする必要があります。
  5. ビジネスロジックはこれらのエラーをキャッチし、プレゼンテーションレイヤーに例外スローする必要があります。
  6. プレゼンテーション層は、入力を検証し、例外を処理するか、ユーザーに悲しいハムスターを表示する必要があります。

再び:

  • データベース->スローエラー
  • ビジネスロジック->キャッチエラーと例外のスロー
  • プレゼンテーション層->検証、例外のスロー、または悲しいメッセージの表示。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.