web-dev-qa-db-ja.com

JetBrainsの@Contractアノテーション

org.jetbrains.annotations.Contractアノテーションは動作しますか? IntelliJ IDEA=それをどのようにサポートしますか?

43

最初に、この注釈はIDEAがエラーの可能性をチェックするためにのみ使用されることを意味します。Javaコンパイラはそれをほぼ完全に無視します(コンパイルされたアーティファクトに含まれますが、効果はありません。

アノテーションの目的は、メソッドが従うcontractを記述することであり、これはIDEAこのメソッドを呼び出す可能性のあるメソッドの問題をキャッチするのに役立ちます。問題のコントラクトはセミコロンで区切られた句のセットで、それぞれが発生が保証される入力と出力を記述します。原因と結果は->で区切られ、メソッドにXを提供する場合、Y will always result。入力はカンマ区切りリストとして記述され、複数の入力の場合を記述します。

可能な入力は_(任意の値)、null!null(非ヌル)、falseおよびtrueであり、可能な出力はこのリストにfailを追加します。

したがって、たとえば、null -> falseは、null入力が提供されると、falseブール値が結果になることを意味します。 null -> false; !null -> trueは、これを拡張して、nullalwaysfalseを返し、null以外の値がalways trueを返すなどと言います。最後に、null -> fail null値を渡すと、メソッドは例外をスローします。

複数の引数の例の場合、null, !null -> failは、2つの引数のメソッドで、最初の引数がnullで2番目の引数がnullでない場合、例外がスローされ、保証されることを意味します。

メソッドがオブジェクトの状態を変更せず、新しい値を返すだけの場合は、pureをtrueに設定する必要があります。

44
dcsohl

公式ドキュメント は、注釈に対してサポートおよび認識されているすべての値の 正式な文法 を指定します。

素人の言葉で:

  • 契約には、1つ以上の条項を関連付けることができます
  • 句はalways[args] -> [effect]
  • 引数は1つ以上の制約であり、any | null | !null | false | trueとして定義されます
  • 効果は1つの制約またはfailのみです

簡単な例を見てみましょう-私のお気に入りの1つは、「このメソッドに渡すものは何でも、例外をスローします」です。

@Contract("_-> fail")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

ここでは、_、または「any」を使用して、このメソッドに何を渡すかに関係なく、例外をスローすることを示しています。

このメソッドが無条件にtrueを返すと嘘をついて言ったらどうでしょうか?

@Contract("_-> true")
public void alwaysBreak(Object o) {
    throw new IllegalArgumentException();
}

IntelliJはそれについていくつかの警告を出します。

enter image description here

voidメソッドにいるときにブール値を返すと言ったのも(明らかに)動揺しています...

enter image description here


@Contractを使用したいと思うのは、主に次の場合です:

  • Trueまたはfalseを返すことを保証したい
  • 制約が与えられた場合にnull以外の値を返すことを保証したい
  • 制約を指定するとnull値を返すことができることを明確にしたい
  • 制約が与えられたときに例外をスローすることを明確にしたい

@Contractが完璧だと言っているわけではありません。それから遠い。特定のコンテキストでは非常に詳細な分析を行うことはできませんが、これをコードベースに含めることで、ツールでこの種の分析を無料で行うことができます。

26
Makoto

_org.jetbrains.annotations.Contract_注釈はどのように機能しますか?

これまでの回答は参考になりますが、あなたの質問の「仕事」という言葉が有効に機能しているとは思いません。つまり、IntelliJがアノテーションを実装するために舞台裏で何をしているのかを説明していないので、独自に簡単に独自のコードを作成できます。

以下のソースコードを見ると、_@NotNull_のように単純に聞こえるかもしれませんが、少し複雑すぎる(または少なくとも冗長な)ように思われるかもしれません。私はあなたに同意します、そして、それは私が一般的に_@Contract_のような「平易で単純な」節(_@NotNull_のような)を避け、代わりにJavaDocが私の前提条件を直接避ける1つの理由です。

Igeneralallyは複雑な契約アノテーションを使用しない —この流行の新しい列車をスキップすることで受ける嫌悪感にもかかわらず、ここにいくつかの理由:

  • アノテーションは複雑な場合があります — e.g。独自の定義に複数のネストされた注釈、および/または Turing completeness の外観を持つ「文法」を持つ。これは、不明瞭/抽象化の層の背後にあるバグの真の犯人を覆い隠し、本来意図された警告を生成しないことにより、コンパイル時に誤った自信をもたらします。
  • 私の以前のポイントと似ていますが、アノテーションは多くの場合、開発者から豊富なロジックを隠します少数のキーワードで、人間にとって理解しにくいコードやデバッグが難しい異常な動作を生成します。
  • 多くの場合、アプリの構成は注釈として マスカレード と見なされます。 Spring framework をご覧ください。
  • コントラクトを定義するための構文は、全体的に見苦しく、Makefile-ishです。たとえば、JetBrainsアノテーション定義とサポートファイルの一部を見てください レポ全体に散らばっています 。多数のXMLファイルと豊富な自己参照に注目してください。特にAndroidとより大きなJavaコミュニティとの間を行き来する注釈の絶え間なく進化する性質を考慮すると、私はその楽しさを書いたりサポートしたりすることはほとんどありません。

考慮すべきいくつかの質問:

  • 注釈のソースコードが注釈を付けるソースそのものの複雑さに近づくと、行き過ぎになりますか?
  • 以下のコードセグメントは、実行時にnullをチェックするよりもはるかに優れていますかスタックトレースで例外を記録しますか?そのようなものを含めると、ユーザーは注釈を定義する依存関係の別のセットを読み通し、理解し、場合によってはバグ修正することができます。

から借りた契約セマンティクスに関する別の長い投稿

_import Java.lang.annotation.Documented;
import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;

/**
 * This annotation can be applied to a package, class or method to indicate that the class fields,
 * method return types and parameters in that element are not null by default unless there is: <ul>
 * <li>An explicit nullness annotation <li>The method overrides a method in a superclass (in which
 * case the annotation of the corresponding parameter in the superclass applies) <li> there is a
 * default parameter annotation applied to a more tightly nested element. </ul>
 * <p/>
 * @see https://stackoverflow.com/a/9256595/14731
 */
@Documented
@Nonnull
@TypeQualifierDefault(
{
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.FIELD,
    ElementType.LOCAL_VARIABLE,
    ElementType.METHOD,
    ElementType.PACKAGE,
    ElementType.PARAMETER,
    ElementType.TYPE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNullByDefault
{
}
_

いつどのような契約を使用すべきですか?

名前から明確な意図を持つ種類に固執することをお勧めします。また、独自のセマンティクスと言語のような定義を持つセットは避けてください。

使用する例 —前のセグメントにもかかわらず—_@NotNull_ですが、すべてのオブジェクトパラメータがnullでなければならない場合に限定してください。

避けるべきソートの例は、AndroidやIntelliJの@Contract(...)などです。私は彼らのIDEが大好きですが、アノテーションの詳細は非常に複雑であり、最終的に私が追跡するためのより多くの問題とプラットフォームの非互換性の原因になりました(ほぼ常に100%しかし、なぜそれを正しくするのがそれほど難しいのですか?)

まとめ/結論

注釈は、ドキュメントを「体系化」しようとしているプログラマーによって明確に生成された素晴らしいアイデアです。ドキュメントがセマンティクスを含むコードになりすぎて、深刻な難問と厄介な状況につながるので、彼らは最近行き過ぎていると感じています。さらに悪いことに、彼らは自分自身の実装で明らかになっている問題を検出できないことにより、コンパイル時の安全性について誤った感覚を与えることがあります。非常に単純なものに固執し、Java(最初に書くことを意図していたもの)ではない言語のように見えるものは避けてください。

参考文献

この簡単なリストは、StackOverflowとWebの両方から得られた、ほとんどの重要な(楽観的!)ソースのミックスであり、私のポイントの一部を示しています。

順不同:

そして、結局のところ、私はあなたの元の質問全体にまだ対処できていないかもしれないことに気づきました:)

17
EntangledLoops