web-dev-qa-db-ja.com

Java assertキーワードは何をしていて、いつ使うべきですか?

アサーションの重要な役割を理解するためのいくつかの 実際の例 は何ですか?

552
Praveen

アサーション assert キーワードによる)がJava 1.4で追加されました。それらは、コード内の不変式の正当性を検証するために使用されます。これらは本番コードでは決して起動されるべきではなく、バグまたはコードパスの誤用を示しています。これらは実行時にJavaコマンドの-eaオプションによってアクティブにすることができますが、デフォルトではオンになっていません。

例:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}
400
Ophidian

原子力発電所を制御するプログラムを書くことになっていると仮定しましょう。最も小さなミスでも壊滅的な結果を招く可能性があることは明らかであるため、コードはバグなしでなければなりません(JVMがバグであると仮定すると、引数のために無料)。

Javaは検証可能な言語ではありません。つまり、操作の結果が完璧になると計算することはできません。この主な理由はポインターです。ポインターはどこでもどこでも指すことができるため、少なくとも適切なコード範囲内でこの正確な値になるように計算することはできません。この問題を考えると、コード全体が正しいことを証明する方法はありません。しかし、あなたができることは、それが起こったときに少なくともすべてのバグを見つけることを証明することです。

このアイデアは、 Design-by-Contract (DbC)パラダイムに基づいています:最初に(数学的な精度で)メソッドの動作を定義し、実際の実行中にテストすることでこれを検証します。例:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

これはうまく動作することは明らかですが、ほとんどのプログラマーはこの内部に隠されたバグを見ることはありません(ヒント:同様のバグのためにAriane Vがクラッシュしました)。現在、DbCでは、関数の入力と出力をalwaysにチェックして、正しく機能することを確認する必要があると定義しています。 Javaは、アサーションを通じてこれを行うことができます。

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

この機能が失敗した場合、それに気付くでしょう。コードに問題があること、それがどこにあるか、何が原因であるかがわかります(例外と同様)。さらに重要なことは、それ以上のコードが間違った値で動作し、それが制御するものに損害を与える可能性がある場合、実行を直ちに停止することです。

Java例外も同様の概念ですが、すべての検証に失敗します。 (実行速度を犠牲にして)さらに多くのチェックが必要な場合は、アサーションを使用する必要があります。これを行うとコードが肥大化しますが、最終的には驚くほど短い開発時間で製品を提供できます(バグを早く修正すればするほど、コストは下がります)。さらに、コード内にバグがある場合は、それを検出します。バグがすり抜けて後で問題を引き起こす方法はありません。

これはバグのないコードを保証するものではありませんが、通常のプログラムよりもはるかに近いものです。

315
TwoThe

アサーションは、コード内のバグを検出するための開発段階のツールです。これらは簡単に削除できるように設計されているので、プロダクションコードには存在しません。したがって、アサーションは、あなたが顧客に提供する「ソリューション」の一部ではありません。それらはあなたがしている仮定が正しいことを確認するための内部チェックです。最も一般的な例は、nullをテストすることです。多くのメソッドはこのように書かれています:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

このようなメソッドでは、ウィジェットは単にnullになることはありません。それでそれがnullであるならば、あなたが追跡する必要があるどこかにあなたのコードのバグがあります。しかし、上記のコードではこれがわかりません。ですから、「安全な」コードを書くという善意の努力で、あなたはバグを隠しているのです。このようなコードを書く方がはるかに良いです。

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

このように、あなたは早くこのバグを捕らえることになるでしょう。開発中にコードをテストするときは、必ずアサーションをオンにしてください。 (そして、あなたの同僚にこれをやるように説得するのも難しいことが多く、これは非常に厄介です。)

さて、あなたの同僚の何人かはこのコードに異議を唱えるでしょう、生産における例外を防ぐためにあなたはまだnullチェックを入れるべきであると主張します。その場合、アサーションはまだ役に立ちます。このように書くことができます:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

このようにして、同僚は本番コードにnullチェックがあることを嬉しく思いますが、開発中、ウィジェットがnullのときにバグを隠すことはもうありません。

これが現実の例です。私はかつて、2つの任意の値が等しいかどうかを比較する方法を書きました。

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

このコードは、thisValueがnullでない場合のequals()メソッドの働きを委任します。しかし、equals()メソッドがnullパラメータを適切に処理することでequals()の規約を正しく満たすことを前提としています。

同僚が私のコードに反対し、私たちのクラスの多くがnullをテストしないバグのあるequals()メソッドを持っていることを私に言って、私はこのメソッドにチェックを入れるべきです。これが賢明な場合、またはエラーを強制する必要がある場合は議論の余地があります。そのためそれを見つけて修正できますが、私は同僚に据え置き、コメント付きのnullチェックを入れました。

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

ここでの追加チェックother != nullは、equals()メソッドがその契約で要求されているようにnullのチェックに失敗した場合にのみ必要です。

バグの多いコードを私たちのコードベースにとどまらせるという知恵について私の同僚と無駄な議論をするのではなく、単純に2つの主張をコードに入れます。これらのアサーションは、開発段階で、私たちのクラスの1つがequals()を正しく実装できなかった場合、私に知らせてくれるので、私はそれを修正することができます:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

心に留めておくべき重要なポイントはこれらです:

  1. アサーションは開発段階のツールのみです。

  2. 主張の要点は、あなたのコードだけでなくあなたの code base にもバグがあるかどうかを知らせることです。 (ここでの主張は実際には他のクラスのバグにフラグを立てるでしょう。)

  3. 私たちのクラスが正しく書かれていると私の同僚が確信していたとしても、ここでの主張はそれでも役に立つでしょう。 nullのテストに失敗する可能性がある新しいクラスが追加されます。このメソッドは、これらのバグにフラグを立てることができます。

  4. 開発中は、自分が書いたコードがアサーションを使用していなくても、常にアサーションを有効にする必要があります。 My IDEは、新しい実行可能ファイルに対して常にデフォルトでこれを実行するように設定されています。

  5. アサーションによって本番環境のコードの動作が変わることはないので、私の同僚はnullチェックがあること、そしてequals()メソッドがバグがあってもこのメソッドが正しく実行されることを嬉しく思います。開発中にバグのあるequals()メソッドをキャッチするので、嬉しいです。

また、失敗する一時的なアサーションを設定してアサーションポリシーをテストする必要があります。そのため、ログファイルまたは出力ストリームのスタックトレースのどちらかを使用して、確実に通知を受けることができます。

58
MiguelMunoz

assertキーワードの機能を説明する多くの良い回答がありますが、「assertキーワードは実際の生活の中でいつ使用すべきか」という本当の質問に答える人はほとんどいません。

答えは、ほとんどありません

概念としてのアサーションは素晴らしいです。良いコードはたくさんのif (...) throw ...ステートメント(そしてObjects.requireNonNullMath.addExactのようなそれらの親戚)を持っています。ただし、特定の設計上の決定により、assertkeyword自体の有用性が大幅に制限されています。

assertキーワードの原動力となる考えは時期尚早の最適化であり、主な機能はすべてのチェックを簡単にオフにできることです。実際、assertチェックはデフォルトでオフになっています。

ただし、本番環境で不変チェックを継続して実行することが非常に重要です。これは、完全なテストカバレッジが不可能であり、すべての本番コードにアサーションが診断と軽減に役立つはずのバグがあるためです。

したがって、パブリックメソッドのパラメータ値をチェックし、IllegalArgumentExceptionをスローするために必要なのと同様に、if (...) throw ...の使用をお勧めします。

時折、不必要なチェックを書くことに誘惑されるかもしれず、それは処理するのに望ましくないほど長い時間がかかる(そしてそれが問題になるのに十分なほど頻繁に呼ばれる)。しかしながら、そのようなチェックはテストを遅くし、これもまた望ましくない。このような時間のかかるチェックは通常単体テストとして書かれています。それにもかかわらず、この理由でassertを使用するのが理にかなっているかもしれません。

assertは、if (...) throw ...よりもきれいできれいであるという理由だけでは使用しないでください(そして、私はきれいできれいが好きなので、それを非常に苦労して言います)。あなたが自分自身で手助けすることができず、アプリケーションの起動方法を制御できる場合は、assertを使用しても構いませんが、常にプロダクションでアサーションを有効にします。確かに、これは私がしがちなことです。私はassertif (...) throw ...のように振る舞うようにするロンボク注釈を強く要求しています。 ここで投票してください

(Rant:JVM開発者たちは、ひどく時期尚早に最適化するコーダの束でした。そのため、JavaプラグインとJVMには非常に多くのセキュリティ問題があると聞いています。彼らはプロダクションコードに基本的なチェックとアサーションを含めることを拒否しました。代金を払う。)

19

これが最も一般的なユースケースです。列挙値をオンにしているとします。

switch (fruit) {
  case Apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

あなたがすべてのケースを扱う限り、あなたは大丈夫です。しかし、いつの日か、誰かがあなたのenumにいちじくを追加し、あなたのswitchステートメントにそれを追加するのを忘れるでしょう。 switch文を終了するまで効果が感じられないので、これは捕まえるのが難しいかもしれないバグを作り出します。しかし、あなたがこのようにあなたのスイッチを書くならば、あなたはすぐにそれをつかむことができます:

switch (fruit) {
  case Apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}
13
MiguelMunoz

アサーションは事後条件と「失敗してはいけない」事前条件をチェックするために使用されます。正しいコードがアサーションを失敗することはありません。それらが引き起こされるとき、彼らはバグを示すべきです(うまくいけば問題の実際の場所があるところに近い場所で)。

アサーションの例としては、特定のメソッドグループが正しい順序で呼び出されることを確認することがあります(たとえば、hasNext()Iteratornext()の前に呼び出される)。

12
Donal Fellows

Javaのassertキーワードは何をするのですか?

コンパイルされたバイトコードを見てみましょう。

我々はそれを結論します:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

以下とまったく同じバイトコードを生成します。

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

ここで、Assert.class.desiredAssertionStatus()-eaがコマンドラインで渡された場合はtrueです。それ以外の場合はfalseです。

最適化されないようにするためにSystem.currentTimeMillis()を使用しています(assert true;)。

合成フィールドは、ロード時にJavaがAssert.class.desiredAssertionStatus()を一度だけ呼び出す必要があるように生成され、その結果をそこにキャッシュします。以下も参照してください。 「静的合成」の意味は何ですか?

それを検証することができます:

javac Assert.Java
javap -c -constants -private -verbose Assert.class

Oracle JDK 1.8.0_45では、合成静的フィールドが生成されました(参照: 「静的合成」の意味は? ):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

静的初期化子と一緒に:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method Java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

そして主な方法は次のとおりです。

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method Java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class Java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method Java/lang/AssertionError."<init>":()V
21: athrow
22: return

私達はそれを結論します:

  • assertに対するバイトコードレベルのサポートはありません。これはJava言語の概念です。
  • assertは、コマンドラインの-Pcom.me.assert=trueを置き換えるためのシステムプロパティ-ea、およびthrow new AssertionError()でうまくエミュレートできます。

Stackクラスからの実世界の例(from Java記事の/ - アサーション から)

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}
6
Björn

アサーションによってコード内の欠陥を検出できます。テストとデバッグのためのアサーションをオンにして、プログラムが実稼働中のときはオフにしておくことができます。

あなたがそれが本当であることを知っているのになぜ何かを主張するのですか?すべてが正しく機能している場合にのみ当てはまります。プログラムに欠陥がある場合、実際にはそうではないかもしれません。プロセスの早い段階でこれを検出すると、何か問題があることがわかります。

assertステートメントには、このステートメントとオプションのStringメッセージが含まれています。

Assert文の構文には2つの形式があります。

assert boolean_expression;
assert boolean_expression: error_message;

これは、アサーションを使用する場所と使用しない場所を決定する基本的な規則です。アサーション - は次の目的で使用されます。

  1. プライベートメソッドの入力パラメータを検証します。パブリックメソッドの場合は _ not _ publicメソッドは間違ったパラメータが渡されると通常の例外を投げます。

  2. ほぼ確実に事実である事実の妥当性を保証するためにプログラムのどこにでもあります。

たとえば、1か2のどちらかにしかならないことが確実な場合は、次のようなアサーションを使用できます。

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. いずれかの方法の最後に事後条件を検証します。つまり、ビジネスロジックを実行した後は、アサーションを使用して、変数または結果の内部状態が期待したものと一致していることを確認できます。たとえば、ソケットまたはファイルを開くメソッドは、最後にアサーションを使用して、ソケットまたはファイルが実際に開かれていることを確認できます。

アサーション はいけません

  1. パブリックメソッドの入力パラメータを検証します。アサーションは常に実行されるとは限らないため、通常の例外メカニズムを使用する必要があります。

  2. ユーザーによって入力されたものに対する制約の検証同上。

  3. 副作用には使用しないでください。

例えば、ここではdoSomething()メソッドを呼び出すという副作用のためにアサーションが使用されているため、これは適切な使用法ではありません。

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

これが正当化できる唯一のケースは、アサーションがコードで有効になっているかどうかを調べようとしているときです。

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}
6
solomkinmv

ここに記載されているすべての素晴らしい回答に加えて、公式のJava SE 7プログラミングガイドには、assertの使用に関するかなり簡潔なマニュアルがあります。アサーションを使用するのが良い(そして、重要なことに、悪い)アイデアであるときの例外的な例と、例外をスローすることとはどう違うのかのいくつかの実例を挙げて。

リンク

5
Ivan Bartsov

開発時にアサートは とても 便利です。あなたのコードが正しく動いているならば何かが できない が起こるときあなたはそれを使います。それは使いやすく、そしてコードの中に永遠に残ることができます、なぜならそれは現実の世界ではオフにされるからです。

実生活でその状態が発生する可能性がある場合は、それを処理する必要があります。

私はそれが大好きですが、Eclipse/Android/ADTでそれを有効にする方法がわかりません。デバッグしても消えているようです。 (これに関するスレッドがありますが、それはADT実行構成に現われない「Java vm」を示します)。

3
John White

これは私がHibernate/SQLプロジェクトのためにサーバーで書いた主張です。エンティティBeanには、isActiveとisDefaultという2つの実質的にブール値のプロパティがありました。それぞれが "Y"、 "N"、またはnullの値を持つことができ、これは "N"として扱われました。ブラウザクライアントがこれら3つの値に制限されていることを確認したいです。そのため、これら2つのプロパティの設定で、このアサーションを追加しました。

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

次のことに注意してください。

  1. この主張は開発段階のためだけのものです。クライアントから不正な値が送信された場合は、実運用に到達するずっと前に、その問題を早期に発見して修正します。アサーションはあなたが早期に発見できる欠陥のためのものです。

  2. この主張は遅く、非効率的です。大丈夫。アサーションは遅くてもかまいません。開発専用のツールなので気にしないでください。アサーションが無効になるので、これはプロダクションコードを遅くしません。 (この点については意見の相違があります。これについては後で説明します。)これは私の次の点につながります。

  3. このアサーションには副作用はありません。私は自分の価値を修正不可能な静的な最終的なセットと比較してテストすることができたかもしれませんが、そのセットはそれが決して使われることはないだろうプロダクションに残っていたでしょう。

  4. このアサーションは、クライアントの適切な動作を確認するために存在します。それで、私たちが生産に到達する時までに、私たちはクライアントが正しく動作していることを確信するでしょう、それで我々は安全に主張をオフにすることができます。

  5. 何人かの人々はこれを尋ねます:アサーションがプロダクションで必要とされていないなら、あなたが終わったときなぜそれらを取り除かないのですか?なぜなら、あなたが次のバージョンで作業を始めるとき、あなたはまだそれらを必要とするでしょう。

一部の人々は、アサーションを使用してはならないと主張してきました。なぜなら、すべてのバグがなくなったことを確信することはできないからです。したがって、assertステートメントを使用しても意味がありません。assertを使用する唯一の利点は、それらをオフにできることです。したがって、この考え方によると、(ほとんど)アサーションを使用しないでください。同意しません。テストがプロダクションに属している場合は、アサートを使用しないでください。しかし、このテストはnotは本番環境に属します。これは本番環境に到達することはほとんどないと思われるバグを捉えるためのものであるため、完了したら安全にオフにすることができます。

ところで、私はこのように書くことができました:

assert value == null || value.equals("Y") || value.equals("N") : value;

これは3つの値に対してのみ問題ありませんが、可能な値の数が増えれば、HashSetバージョンはより便利になります。私は効率について私の主張をするためにHashSetバージョンを選びました。

3
MiguelMunoz

アサーションは基本的にアプリケーションをデバッグするために使用されるか、アプリケーションの有効性をチェックするためにアプリケーションの例外処理の代わりに使用されます。

アサーションは実行時に機能します。概念全体を非常に簡単に説明できる単純な例は、次のとおりです。 - assertキーワードはJavaで何をするのでしょうか。(WikiAnswers).

2
SBTec

アサーションはデフォルトで無効になっています。それらを有効にするためには-eaオプションを付けてプログラムを実行しなければなりません(粒度は変えることができます)。たとえば、Java -ea AssertionsDemoです。

アサーションを使用するためのフォーマットは2つあります。

  1. シンプル: assert 1==2; // This will raise an AssertionError
  2. より良い:assert 1==2: "no way.. 1 is not equal to 2";これは与えられたメッセージも表示されたAssertionErrorを発生させますので、より良いです。実際の構文はassert expr1:expr2で、expr2は値を返す任意の式にすることができますが、メッセージを表示するためだけに使用することが多くあります。
2
Chandan Purohit

要約すると(そしてこれはJavaだけでなく多くの言語に当てはまる):

"assert"は主にデバッグプロセス中のソフトウェア開発者によるデバッグの補助として使用されます。アサートメッセージは表示されません。多くの言語はコンパイル時オプションを提供しています。これにより、 "本番"コードの生成に使用するために、すべての "アサーション"が無視されます。

「例外」は、論理エラーを表すかどうかにかかわらず、あらゆる種類のエラー条件を処理するための便利な方法です。 「どこからでも、他の誰かが彼らを「捕まえる」準備ができていることを期待しています。制御は、例外をスローしたコードからキャッチャーの手引きに直接移ります。 (そしてキャッチャーは、行われた呼び出しの完全なバックトレースを見ることができます。)

さらに、そのサブルーチンの呼び出し元は、サブルーチンが成功したかどうかを確認する必要はありません。 "ここにいる場合は、成功したはずです。 」 この単純な戦略により、コード設計とデバッグがはるかに簡単になります。

例外は、致命的エラー条件が「ルールの例外」であることを都合よく可能にします。そして、それらが "規則の例外でもある... "飛球! "であるコードパスによって扱われるために

1
Mike Robinson

基本的に、「assert true」は成功し、「assert false」は失敗します。これがどのように機能するかを見てみましょう。

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}
0
user891193

アサーションはオフになる可能性があるチェックです。めったに使われません。どうして?

  • あなたがそれらを制御することができないので、それらはパブリックメソッド引数をチェックするために使用されてはいけません。
  • このようなチェックは非常に高速で、保存するものはほとんどないため、result != nullのような単純なチェックには使用しないでください。

それで、何が残っていますか? 高価な 実際に条件をチェックする 期待される が真となる。良い例は、RBツリーのようなデータ構造の不変条件です。実際には、JDK 8のConcurrentHashMapでは、TreeNodesに対してそのような意味のあるアサーションがいくつかあります。

  • 実行時間を簡単に左右する可能性があるため、本番環境でこれらのスイッチをオンにすることは望ましくありません。
  • テスト中にそれらをオンまたはオフにすることができます。
  • あなたは間違いなくコードで作業するときにそれらをオンにしたいです。
0
maaartinus