メソッドが不正な入力を返すことができないことがわかっている場合でも、メソッド呼び出しの戻り値を検証する必要がありますか?


30

呼び出しているメソッドがそのような期待を満たしているとわかっていても、メソッドが呼び出した期待値を満たしていることを検証することで、メソッド呼び出しの戻り値に対して防御する必要があるかどうか疑問に思っています。

与えられた

User getUser(Int id)
{
    User temp = new User(id);
    temp.setName("John");

    return temp;
}

私はすべき

void myMethod()
{
    User user = getUser(1234);
    System.out.println(user.getName());
}

または

void myMethod()
{
    User user = getUser(1234);

    // Validating
    Preconditions.checkNotNull(user, "User can not be null.");
    Preconditions.checkNotNull(user.getName(), "User's name can not be null.");

    System.out.println(user.getName());
}

概念レベルでこれを求めています。私が呼び出しているメソッドの内部動作を知っている場合。私が書いたのか、調べたからです。そして、それが返す可能性のある値の論理は私の前提条件を満たします。検証をスキップするのが「より良い」または「より適切な」のか、それとも常に合格する必要がある場合でも、現在実装しているメソッドを続行する前に間違った値から防御する必要がありますか?


すべての答えからの私の結論(気軽にあなた自身のものに来てください):

いつアサートする
  • このメソッドは過去に誤動作することが示されています
  • メソッドは信頼できないソースからのものです
  • このメソッドは他の場所から使用され、事後条件を明示的に述べていません
次の場合にアサートしないでください:
  • このメソッドはあなたのメソッドに近いものです(詳細は選択した回答をご覧ください)
  • メソッドは、適切なドキュメント、タイプセーフティ、単体テスト、事後条件チェックなどの契約を明示的に定義します
  • パフォーマンスが重要です(この場合、デバッグモードのアサートはハイブリッドアプローチとして機能します)

1
%100のコードカバレッジを目指してください!
ジャレッドバロウズ

9
なぜ1つの問題(Null)だけに注目しているのですか?名前が""(nullではなく空の文字列)またはの場合、おそらく同様に問題があります"N/A"。結果を信頼するか、適切に妄想する必要があります。
–MSalters

3
コードベースには命名スキームがあります:getFoo()はnullを返さないことを約束し、getFooIfPresent()はnullを返すことがあります。
pjc50

コードの健全性の推定はどこから来ますか?エントリ時に検証されるため、または値が取得される構造のために、値は常にnullでないと仮定していますか?さらに重要なことは、考えられるすべてのエッジケースシナリオをテストしていますか?悪意のあるスクリプトの可能性を含みますか?
ジボブズ

回答:


42

これは、getUserとmyMethodがどの程度変化する可能性があるか、さらに重要なことには、それらが互いに独立して変化する可能性の程度に依存します。

あなたは何とかgetUserメソッドは、これまでに、これまで将来変更しないことを確かに知っている場合は、[はい、それはそれは、検証の時間を無駄にしている限りとして、それを検証し、時間の無駄だi直後に3の値を持っているi = 3;の文。現実には、あなたはそれを知りません。ただし、知っていることがいくつかあります。

  • これらの2つの機能は「一緒に」動作しますか?言い換えれば、彼らは彼らを維持している同じ人を持っていますか、彼らは同じファイルの一部ですか?したがって、彼らは自分自身で互いに「同期」したままになる可能性がありますか?この場合、2つの関数が変更されるか、異なる数の関数にリファクタリングされるたびに変更(および潜在的にバグ)しなければならないコードを単に意味するため、検証チェックを追加するのはおそらくやり過ぎです。

  • getUser関数は、特定のコントラクトで文書化されたAPIの一部であり、myMethodは別のコードベースのAPIのクライアントにすぎませんか?その場合、そのドキュメントを読んで、戻り値を検証する(または入力パラメーターを事前検証する)必要があるかどうか、または盲目的にハッピーパスをたどることが本当に安全かどうかを確認できます。ドキュメントでこれが明確になっていない場合は、メンテナーに修正を依頼してください。

  • 最後に、この特定の関数が過去に突然かつ予期せずに動作を変更した場合、コードを壊すような方法で、あなたはそれについて偏執する権利があります。バグは集中する傾向があります。

両方の機能の元の作成者であっても、上記のすべてが適用されることに注意してください。これらの2つの関数が残りの人生で「一緒に生きる」ことが期待されているのか、それとも別々のモジュールにゆっくりと移行するのか、または古いバグで自分の足を撃ったのかはわかりませんgetUserのバージョン。しかし、あなたはおそらくかなりまともな推測をすることができます。


2
また:getUser後で変更してnullを返す可能性がある場合、明示的なチェックは「NullPointerException」から取得できない情報と行番号を提供しますか?
user253751

検証できるものは2つあるようです。(1)バグがないため、メソッドが適切に動作すること。(2)メソッドが契約を尊重すること。#1の検証が過剰であることを確認できます。代わりに#1を実行するテストが必要です。これが、i = 3;を検証しない理由です。私の質問は#2に関連しています。私はそのためのあなたの答えが好きですが、同時に、それはあなたの判断の答えを使用しています。アサーションを書くのに少し時間を浪費する以外に、契約を明示的に検証することから生じる他の問題を見ますか?
ディディエA.

他の唯一の「問題」は、契約の解釈が間違っているか、間違っている場合、すべての検証を変更する必要があるということです。しかし、それは他のすべてのコードにも当てはまります。
Ixrec

1
群衆に逆らうことはできません。この回答は、私の質問のトピックをカバーする上で間違いなく最も正しいものです。すべての回答、特に@MikeNakisを読んで、Ixrecの最後の2つの箇条書きのいずれかがある場合は、少なくともデバッグモードで前提条件をアサートする必要があると思います。最初の箇条書きに直面している場合、おそらく前提条件を主張するのはやり過ぎです。最後に、適切なドキュメント、タイプセーフティ、単体テスト、または事後条件チェックなどの明示的な契約が与えられている場合、前提条件を主張するべきではありません。
ディディエA.

22

Ixrecの答えは良いですが、検討する価値があると思うので、別のアプローチを取ります。この議論の目的のために、私はアサーションについて話しますPreconditions.checkNotNull()。それはあなたが伝統的に知られている名前だからです。

私が提案したいことは、プログラマーはしばしば、何かが特定の方法で動作することを確信している程度を過大評価し、そのように動作しないことの結果を過小評価するということです。また、プログラマはドキュメントとしてのアサーションの力に気付かないことがよくあります。その後、私の経験では、プログラマは物事を必要以上に頻繁に主張しません。

私のモットーは次のとおりです。

あなたが常に自問すべき質問は、「これを主張すべきか?」ではありません。しかし、「断言するのを忘れたものはありますか?」

当然、何かが特定の方法で動作すること完全に確信している場合、実際にそのように動作したと断言することは控えます。何も信用できない場合、ソフトウェアを書くことは実際には不可能です。

ただし、このルールにも例外があります。不思議なことに、Ixrecの例を挙げると、確かに特定の範囲内にあるint i = 3;アサーションに従うことが完全に望ましいタイプの状況が存在しiます。これは、iさまざまなwhat-ifシナリオを試みている人によって変更される可能性がある状況でありi、続くコードは特定の範囲内の値を持つことに依存しており、次のコードをすぐに見てもすぐにはわかりません許容範囲は次のとおりです。だから、int i = 3; assert i >= 0 && i < 5;最初は無意味に思えるかもしれませんが、考えてみると、で遊ぶことができるということとi、滞在しなければならない範囲を教えてくれます。アサーションはドキュメントの最良のタイプです。なぜなら、machineによって強制されるため、完全な保証であり、コード一緒にリファクタリングされるため、常に適切です。

あなたのgetUser(int id)例は、このassign-constant-and-assert-in-range例のそれほど遠くない概念的なものではありません。定義によれば、予期した動作とは異なる動作が発生した場合にバグが発生します。確かに、すべてをアサートすることはできないため、デバッグが行われる場合があります。しかし、エラーを迅速に発見すればするほど、コストが削減されることは、業界では十分に確立された事実です。あなたが尋ねるべき質問はgetUser()、nullを決して返さない(そしてnull名のユーザーを返さない)ことだけでなく、他のコードでどのような悪いことが起こるかを確認することです実際、ある日、予想外の何かを返します。そして、コードの残りの部分をすばやく調べて、何が期待されていたかを正確に知ることは、どれほど簡単なことでしょうgetUser()

予期しない動作によりデータベースが破損する場合、それが起こらないことを101%確信している場合でも、アサートする必要があるかもしれません。nullユーザー名によって、さらに数千行下の奇妙で不可解な追跡不可能なエラーが発生し、数十億クロックサイクルが遅れる場合、できるだけ早く失敗するのが最善です。

ですから、Ixrecの答えに異論はありませんが、アサーションに費用がかからないという事実を真剣に検討することをお勧めします。


2
はい。アサートします。多かれ少なかれこの答えを与えるためにここに来ました。++
ラバーダック

2
@SteveJessopこれをに修正し... && sureness < 1)ます。
マイクナキス

2
@MikeNakis:ユニットテストを書くとき、常に悪夢だ、あなたしている101%はあなたが右、あなたがしているしているときのは、インターフェイスで許可されますが、実際に任意の入力から生成する;-)とにかく不可能という戻り値を間違いなく間違っています!
スティーブジェソップ

2
答えで言及するのを完全に忘れてしまったことを指摘してくれた+1。
Ixrec

1
@DavidZそれは正しいです、私の質問はランタイム検証を想定しています。しかし、デバッグではメソッドを信頼せず、prodではメソッドを信頼すると言うのは、このトピックに関するおそらく良い視点です。したがって、Cスタイルのアサートは、この状況に対処するための良い方法かもしれません。
ディディエA.

8

明確ないいえ。

呼び出し元は、呼び出している関数がそのコントラクトを尊重しているかどうかをチェックしてはなりません。その理由は単純です。呼び出し元が非常に多くなる可能性があり、他の関数の結果をチェックするためのロジックを複製することは、すべての呼び出し元の概念的な観点から非現実的であり、間違いですら間違いです。

チェックを行う場合、各関数は独自の事後条件をチェックする必要があります。事後条件により、機能を正しく実装したこと、およびユーザーが機能の契約に依存できることを確信できます。

特定の関数がnullを返すことを意図していない場合、これを文書化し、その関数のコントラクトの一部にする必要があります。これがこれらの契約のポイントです。ユーザーが自分の関数を記述する際にハードルが少なくなるように、信頼できる不変条件をユーザーに提供します。


私は一般的な感情に同意しますが、戻り値を検証することが間違いなく理にかなっている状況があります。たとえば、外部Webサービスを呼び出す場合、少なくとも回答の形式(xsdなど)を検証したい場合があります
AK_

あなたは防御的なプログラミングよりも契約によるデザインのスタイルを提唱しているようです。これは素晴らしい答えだと思います。メソッドに厳密な契約があれば、信頼できます。私の場合はそうではありませんが、変更することもできます。私はできなかったと言いますか?私は実装を信頼すべきだと主張しますか?docで明示的にnullを返さない、または実際に事後条件チェックがあるとは記載されていませんか?
ディディエA.

私の意見では、あなたは最良の判断を使用し、著者が正しいことをすることをどれだけ信頼しているのか、関数のインターフェースがどのくらい変わる可能性があるのか​​、時間の制約がどれだけ厳しいのかなど、さまざまな側面に重みを付ける必要があります。そうは言っても、ほとんどの場合、関数の作成者は、文書化されていなくても、関数のコントラクトがどうあるべきかについてかなり明確な考えを持っています。このため、著者が正しいことを行うことを最初に信頼し、関数が適切に記述されていないことがわかった場合にのみ防御的になるべきだと言います。
ポール

最初に他の人に正しいことをすることを信頼することで、仕事を早く終わらせることができます。そうは言っても、私が書いた種類のアプリケーションは、問題が発生したときに簡単に修正できます。より安全性が重要なソフトウェアで作業している人は、私が寛大すぎて、より防御的なアプローチを好むと考えるかもしれません。
ポール

5

nullがgetUserメソッドの有効な戻り値である場合、コードは例外ではなくIfでそれを処理する必要があります-これは例外的なケースではありません。

nullがgetUserメソッドの有効な戻り値でない場合、getUserにバグがあります-getUserメソッドは例外をスローするか、修正する必要があります。

getUserメソッドが外部ライブラリの一部であるか、そうでなければ変更できず、nullリターンの場合に例外をスローする場合は、クラスとメソッドをラップしてチェックを実行し、例外をスローして、 getUserの呼び出しはコード内で一貫しています。


nullはgetUserの有効な戻り値ではありません。getUserを調べることで、実装がnullを返さないことがわかりました。私の質問は、検査を信頼してメソッドにチェックを入れないか、検査を疑ってまだチェックする必要があるかです。メソッドが将来変更される可能性もあり、nullを返さないという私の期待を破ります。
ディディエA.

また、getUserまたはuser.getNameが変更されて例外がスローされた場合はどうなりますか?チェックが必要ですが、ユーザーを使用する方法の一部ではありません。getUserコードの破壊に対する将来の変更が心配な場合は、getUserメソッド(およびUserクラス)をラップして、契約を実施します。また、ユニットのテストを作成して、クラスの動作に関する仮定を確認します。
マシューC

@didibus答えを言い換えると... スマイリー絵文字はgetUserの有効な戻り値ではありません。getUserを調べることで、実装が笑顔の絵文字を返さないことがわかりました。私の質問は、検査を信頼してメソッドにチェックを入れないか、検査を疑ってまだチェックする必要があるかです。また、将来的に方法が変更され、笑顔の絵文字を返さないという私の期待を破ることも可能です。呼び出された関数がその契約を満たしていることを確認するのは、呼び出し元の仕事ではありません。
ビンスオサリバン

@ VinceO'Sullivan「契約」が問題の中心にあるようです。そして、私の質問は、明示的な契約に対する暗黙的な契約に関するものです。intを返すメソッドは、文字列を取得していないと断言しません。しかし、正の整数のみが必要な場合は、どうすればよいですか?メソッドが検査したので、メソッドが負のintを返さないことは暗黙的です。署名またはドキュメントから、そうでないことは明らかではありません。
ディディエA.

1
契約が暗黙的であるか明示的であるかは関係ありません。有効なデータが与えられたときに、呼び出されたメソッドが有効なデータを返すことを検証するのは、呼び出し元のコードの仕事ではありません。あなたの例では、負の初期値が間違った答えではないことを知っていますが、正の整数が正しいかどうか知っていますか?メソッドが機能していることを証明するために、本当にいくつのテストが必要ですか?唯一の正しいコースは、メソッドが正しいと仮定することです。
ビンスオサリバン

5

私はあなたの質問の前提がオフであると主張します:そもそもヌル参照を使用するのはなぜですか?

ヌルオブジェクトパターンは表していないオブジェクトを返す、あなたのユーザーのためではなく、戻り値はnull:基本的に何もしないリターン・オブジェクトへの道である「はユーザーを。」

このユーザーは非アクティブで、空の名前、および「ここには何もない」ことを示すその他の非ヌル値があります。主な利点は、オブジェクトを使用するだけで、チェック、ロギングなどを無効にするプログラムをポイ捨てする必要がないことです。

なぜアルゴリズムはヌル値を気にする必要があるのですか?手順は同じである必要があります。ゼロ、空の文字列などを返すnullオブジェクトを使用するのも同じくらい簡単ではるかに明確です。

nullのコードチェックの例を次に示します。

User u = getUser();
String displayName = "";
if (u != null) {
  displayName = u.getName();
}
return displayName;

nullオブジェクトを使用したコードの例を次に示します。

return getUser().getName();

どちらが明確ですか?二つ目はそうだと思います。


質問にはタグが付けられていますが、参照を使用する場合、C ++ではnullオブジェクトパターンが本当に役立つことに注意する価値があります。Java参照はC ++ポインターに似ており、nullを許可します。C ++参照は保持できませんnullptr。C ++のnull参照に類似したものが必要な場合、nullオブジェクトパターンがそれを達成する唯一の方法です。


Nullは、私が主張するものの単なる例です。空でない名前が必要な場合があります。私の質問はまだ適用されますが、それを検証しますか?または、intを返すもので、負のintを使用できないか、指定された範囲内にある必要があります。言うまでもなく、それをヌルの問題と考えないでください。
ディディエA.

1
メソッドが間違った値を返すのはなぜですか?これを確認する必要がある唯一の場所は、アプリケーションの境界、つまりユーザーと他のシステムとのインターフェースです。それ以外の場合は、例外があります。

想像してみてください:int i = getCount; float x = 100 / i; メソッドを記述したか検査したためにgetCountが0を返さないことがわかっている場合、0のチェックをスキップする必要がありますか?
ディディエA.

@didibus:getCount() ドキュメントが0を返さないという保証がある場合、チェックをスキップできます。誰かが重大な変更を行うことを決定し、それを呼び出すためにそれを呼び出すすべてを更新しようとする状況を除いて、将来はそうしません新しいインターフェース。現在コードが何をしているのかを知ることは役に立ちませんが、コードを検査する場合、検査するコードはの単体テストですgetCount()。0を返さないようにテストする場合、テストが失敗するため、0を返す将来の変更は重大な変更と見なされます。
スティーブジェソップ

nullオブジェクトがnullよりも優れているかどうかは私には明らかではありません。ヌルカモは鳴くかもしれませんが、それはカモではありません-実際、意味的には、カモのアンチテーゼです。nullオブジェクトを導入すると、そのクラスを使用するコードは、そのオブジェクトのいずれかがnullオブジェクトであるかどうかを確認する必要があります。たとえば、セットにnullがある場合、いくつありますか?これは、失敗を延期し、エラーを難読化する方法のように思えます。リンクされた記事は、「nullチェックを避けてコードを読みやすくするためだけに」nullオブジェクトの使用に対して特に警告しています。
-sdenham

1

一般に、それは主に次の3つの側面に依存すると言えます。

  1. 堅牢性:呼び出しメソッドはnull値に対応できますか?null値が生じる可能性がある場合、RuntimeExceptionチェックをお勧めします-複雑性が低く、呼び出し/呼び出しメソッドが同じ作成者によって作成され、null予想されない場合(たとえば、両方privateが同じパッケージにある場合)。

  2. コードの責任:開発者AがgetUser()メソッドに責任を負い、開発者Bがメソッドを(ライブラリの一部として)使用する場合、その値を検証することを強くお勧めします。開発者Bが潜在的なnull戻り値をもたらす変更について知らないかもしれないからです。

  3. 複雑さ:プログラムまたは環境の全体的な複雑さが高いほど、戻り値を検証することをお勧めします。null呼び出し元のメソッドのコンテキストでは今日ではありえないと確信していてもgetUser()、別のユースケースのために変更する必要があるかもしれません。数か月または数年が過ぎ、数千行のコードが追加されると、これは非常にわなになります。

さらに、潜在的なnull戻り値をJavaDocコメントに文書化することをお勧めします。ほとんどのIDEでJavaDocの説明が強調表示されているため、これはを使用するすべてのユーザーに役立つ警告となりますgetUser()

/** Returns ....
  * @param: id id of the user
  * @return: a User instance related to the id;
  *          null, if no user with identifier id exists
 **/
User getUser(Int id)
{
    ...
}

-1

絶対にする必要はありません。

たとえば、戻り値がnullになることはないことが既にわかっている場合-なぜnullチェックを追加するのでしょうか?nullチェックを追加しても何も壊れませんが、冗長です。回避できる限り、冗長なロジックをコーディングしないことをお勧めします。


1
私は知っていますが、それは他のメソッドの実装を調べたり書いたりしたからです。メソッドのコントラクトは、できないと明示的に述べていません。そのため、たとえば将来変更される可能性があります。または、そうしない場合でもnullを返すバグが発生する可能性があります。
ディディエA.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.