web-dev-qa-db-fra.com

Annotation @Contract de JetBrains

Comment le org.jetbrains.annotations.Contract travail d'annotation? Comment IntelliJ IDEA le prend-il en charge?

43
Francesco Menzani

Tout d'abord, je dois dire que cette annotation est uniquement destinée à IDEA à utiliser pour vérifier les erreurs possibles. Le compilateur Java l'ignorera presque entièrement (il ' ll sera dans l'artefact compilé mais n'a aucun effet.) Cela dit ...

Le but de l'annotation est de décrire un contrat auquel la méthode obéira, ce qui aide IDEA à détecter les problèmes dans les méthodes qui peuvent appeler cette méthode. Le contrat en question est un ensemble de clauses séparées par des points-virgules, chacune décrivant une entrée et une sortie dont la garantie est garantie. La cause et l'effet sont séparés par ->, et décrivez le cas où lorsque vous fournissez X à la méthode, Y donnera toujours résultat. L'entrée est décrite comme une liste séparée par des virgules, décrivant le cas de plusieurs entrées.

Les entrées possibles sont _ (n'importe quelle valeur), null, !null (non nul), false et true, et les sorties possibles ajoutent fail à cette liste.

Ainsi, par exemple, null -> false signifie que, à condition qu'une entrée null, un booléen false soit le résultat. null -> false; !null -> true développe ceci pour dire que null va toujours retourner false et une valeur nonnull sera toujours return true, etc. Enfin, null -> fail signifie que la méthode lèvera une exception si vous lui passez une valeur nulle.

Pour un exemple à plusieurs arguments, null, !null -> fail signifie que, dans une méthode à deux arguments, si le premier argument est nul et le second n'est pas nul, une exception sera levée, garantie.

Si la méthode ne change pas l'état de l'objet, mais renvoie simplement une nouvelle valeur, vous devez définir pure sur true.

44
dcsohl

La documentation officielle spécifie la grammaire formelle de toutes les valeurs prises en charge et reconnues pour l'annotation.

En termes simples:

  • Un contrat peut être associé à une ou plusieurs clauses
  • Une clause est toujours[args] -> [effect]
  • Les args sont une ou plusieurs contraintes, définies comme any | null | !null | false | true
  • Les effets ne sont qu'une seule contrainte ou fail

Passons en revue un exemple rapide - l'un de mes favoris est, "Quoi que vous passiez dans cette méthode, il lèvera une exception."

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

Ici, nous utilisons _, ou "any", pour indiquer que, indépendamment de ce que nous transmettons à cette méthode, nous allons lever une exception.

Et si nous mentions et disions que cette méthode allait renvoyer true sans condition?

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

IntelliJ émet quelques avertissements à ce sujet.

enter image description here

C'est aussi (évidemment) bouleversé que nous avons dit que nous retournions un booléen lorsque nous sommes dans une méthode void. ..

enter image description here


La plupart du temps, vous vous retrouverez à vouloir utiliser @Contract est quand:

  • Vous voulez garantir que vous retournez vrai ou faux
  • Vous voulez garantir que vous renvoyez une valeur non nulle compte tenu des contraintes
  • Vous souhaitez indiquer clairement que vous pouvez renvoyer une valeur nulle compte tenu des contraintes
  • Vous voulez indiquer clairement que vous lèverez une exception compte tenu des contraintes

Cela ne veut pas dire que @Contract est parfait; loin de là. Il ne peut pas effectuer une analyse très approfondie dans certains contextes, mais l'avoir dans votre base de code permet à votre outillage de faire ce type d'analyse gratuitement.

26
Makoto

Comment fonctionne l'annotation org.jetbrains.annotations.Contract?

Bien que les réponses précédentes soient informatives, je ne pense pas qu'elles abordent le mot opérationnel "travail" dans votre question. Autrement dit, ils n'expliquent pas ce que fait IntelliJ en arrière-plan pour implémenter leurs annotations afin que vous puissiez facilement créer le vôtre à partir de zéro.

Si vous regardez le code source ci-dessous, vous pourriez penser qu'il semble un peu trop compliqué (ou du moins verbeux) pour quelque chose d'aussi simple que @NotNull. Je serais d'accord avec vous, et c'est une des raisons pour lesquelles j'évite généralement les clauses similaires à @Contract Qui ne sont pas "simples et simples" (comme @NotNull) Et à la place JavaDoc mes prérequis directement.

I en général décourage l'utilisation d'annotations de contrat complexes - malgré la haine que je pourrais avoir pour avoir sauté ce nouveau train branché - et voici quelques raisons pour lesquelles:

  • Les annotations peuvent être complexes - par exemple. ayant plusieurs annotations imbriquées dans leurs propres définitions et/ou une "grammaire" avec l'apparence de Turing complétude . Cela peut conduire à un faux sentiment de confiance au moment de la compilation en masquant le véritable coupable d'un bogue derrière des couches d'obscurité/abstraction et en ne générant pas les avertissements initialement prévus.
  • Similaire mais différent de mon point précédent, les annotations cachent souvent une logique abondante au développeur dans une poignée de mots clés, produisant du code difficile à comprendre pour les humains et/ou un comportement inhabituel qui peut être difficile à déboguer.
  • La configuration de l'application est souvent considérée comme une mascarade comme des annotations. Jetez un œil au framework Spring .
  • La syntaxe pour définir les contrats est en gros (IMHO) assez laide et Makefile-ish. Par exemple, jetez un coup d'œil à certaines des définitions d'annotation JetBrains et des fichiers de support dispersés sur son dépôt . Remarquez les nombreux fichiers XML et l'auto-référence abondante? Je dirais à peine que c'est amusant d'écrire et de soutenir, surtout compte tenu de la nature en constante évolution des annotations dirigées par les allers-retours entre Android et la plus grande communauté Java.

Quelques questions à considérer:

  • Est-ce que cela va trop loin lorsque le code source d'une annotation approche de la complexité de la source même qu'elle annote?
  • Le ​​segment de code ci-dessous est-il vraiment bien meilleur que de simplement vérifier la null lors de l'exécution et de consigner les exceptions avec stacktraces? Inclure quelque chose comme ça oblige vos utilisateurs à lire, comprendre et éventuellement corriger un autre ensemble de dépendances qui définissent vos annotations.

Emprunté à un autre long article sur la sémantique des contrats qui, je dirais, ne fait que renforcer mon point:

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
{
}

Quand et quels contrats devez-vous utiliser?

Je suggère de m'en tenir au genre dont les intentions sont claires comme le cristal de leur nom, et d'éviter celles qui ont leur propre ensemble de définitions sémantiques et langagières.

n exemple de celui à utiliser - malgré le segment précédent - est @NotNull, Mais gardez-le limité à quand tous les paramètres d'objet doivent être nuls.

n exemple du genre à éviter sont ceux comme Android et @Contract(...) d'IntelliJ. Bien que j'aime leurs IDE, les détails de leurs annotations sont assez compliqués et se sont finalement transformés en une source de plus de problèmes et d'incompatibilité de plate-forme pour moi à retrouver (certes causée par ma propre ignorance dans la création de nouveaux contrats presque 100% du temps, mais pourquoi est-il si difficile de bien faire les choses?)

Résumé/Conclusion

Les annotations sont une excellente idée clairement générée par les programmeurs qui cherchent à "codifier" leur documentation. Je pense qu'ils sont allés trop loin ces derniers temps, transformant la documentation en code sémantique porteur de graves énigmes et de situations délicates. Pire encore, ils confèrent parfois un faux sentiment de sécurité au moment de la compilation en ne détectant pas les problèmes manifestes dans leurs propres implémentations. Restez dans le très simple et évitez tout ce qui ressemble à une langue qui n'est pas Java (c'est ce que vous aviez l'intention d'écrire en premier lieu).

Lectures complémentaires

Cette brève liste est un mélange de sources principalement critiques (avec optimisme!) De StackOverflow et du Web qui, selon moi, illustrent certains de mes points.

Dans aucun ordre particulier:

Et après tout cela, je viens de réaliser que je n'ai peut-être toujours pas répondu à votre question initiale dans son intégralité :)

17
EntangledLoops