parameter java intellij-idea jetbrains

parameter - @notnull java



Anotación @Contract de JetBrains (3)

¿Cómo funciona la anotación org.jetbrains.annotations.Contract ?

Aunque las respuestas anteriores son informativas, no creo que aborden la palabra operativa "trabajo" en su pregunta. Es decir, no explican lo que IntelliJ está haciendo entre bambalinas para implementar sus anotaciones, de modo que usted pueda construir su propio fácilmente desde cero.

Si echas un vistazo al código fuente a continuación, podrías pensar que parece un poco demasiado complicado (o al menos detallado) para algo tan simple como @NotNull . Estoy de acuerdo con usted, y es una de las razones por las que generalmente evito las cláusulas similares a @Contract que no son "simples y sencillas" (como @NotNull ) y en su lugar JavaDoc mis prereqs directamente.

En general, desaliento el uso de complejas anotaciones de contratos, a pesar del odio que podría recibir por saltarme este nuevo tren de moda, y aquí algunas razones por las que:

  • Las anotaciones pueden ser complejas, por ejemplo, tener múltiples anotaciones anidadas en sus propias definiciones y / o una "gramática" con la apariencia de que Turing está completo . Esto puede llevar a una falsa sensación de confianza en tiempo de compilación al enmascarar al verdadero culpable de un error detrás de las capas de oscuridad / abstracción y no generar las advertencias originalmente previstas.
  • De manera similar pero diferente a mi punto anterior, las anotaciones a menudo ocultan una lógica copiosa del desarrollador en un puñado de palabras clave, lo que produce un código difícil de entender para los humanos y / o un comportamiento inusual que puede ser difícil de depurar.
  • La configuración de la aplicación a menudo se ve enmascarada como anotaciones. Eche un vistazo al marco de primavera .
  • La sintaxis para definir los contratos es en gran medida (en mi humilde opinión) bastante fea y Makefile-ish. Por ejemplo, eche un vistazo a algunas de las definiciones de anotación de JetBrains y los archivos de soporte repartidos en su repositorio . ¿Te fijas en los numerosos archivos XML y en la gran autorreferencia? Difícilmente podría llamarlo divertido escribirlo y apoyarlo, especialmente teniendo en cuenta la naturaleza en constante evolución de las anotaciones encabezadas por el intercambio entre Android y la comunidad más grande de Java.

Algunas preguntas a considerar:

  • ¿Está yendo demasiado lejos cuando el código fuente de una anotación se acerca a la complejidad de la misma fuente que anota?
  • ¿El segmento de código es mucho mejor que el de comprobar nulo en tiempo de ejecución y registrar excepciones con stacktraces? Incluir algo así obliga a los usuarios a leer, comprender y, posiblemente, corregir errores en otro conjunto de dependencias que definen sus anotaciones.

Tomado de otro post extenso sobre semántica de contratos que me gustaría argumentar solo sirve a mi punto:

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

¿Cuándo y qué contratos debe usar?

Sugiero que se adhieran al tipo cuyas intenciones son claras como el cristal de su nombre, y evite aquellas con su propio conjunto de semánticas y definiciones de lenguaje.

Un ejemplo de uno para usar, a pesar del segmento anterior, es @NotNull , pero debe limitarse a cuando todos los parámetros del objeto deben ser nulos.

Un ejemplo del tipo que se debe evitar son aquellos como Android y el @Contract(...) de IntelliJ @Contract(...) . Si bien me encantan sus IDE, los detalles de sus anotaciones son bastante complicados y, en última instancia, se convirtieron en una fuente de más problemas e incompatibilidad de plataforma para que yo los rastreara (es cierto que esto se debe a mi propia ignorancia al crear nuevos contratos casi el 100% del tiempo). pero ¿por qué es tan difícil hacerlo bien?)

Conclusion resumen

Las anotaciones son una gran idea generada claramente por los programadores que buscan "codificar" su documentación. Siento que han ido demasiado lejos últimamente, convirtiendo la documentación en un código semántico que lleva a algunos enigmas serios y situaciones incómodas. Lo que es peor, a veces dan una falsa sensación de seguridad en tiempo de compilación al no detectar los problemas que se manifiestan en sus propias implementaciones. Aférrate a lo simple y evita todo lo que parezca un lenguaje que no es Java (que es lo que intentabas escribir en primer lugar).

Otras lecturas

Esta breve lista es una mezcla de fuentes principalmente críticas (¡w / optimismo!) Tanto de StackOverflow como de la web que creo que ilustran algunos de mis puntos.

Sin ningún orden en particular:

Y después de todo eso, me di cuenta de que aún no había podido abordar su pregunta original en su totalidad :)

¿Cómo funciona la anotación org.jetbrains.annotations.Contract ? ¿Cómo lo soporta IntelliJ IDEA?


En primer lugar, debería decir que esta anotación es solo para IDEA para verificar posibles errores. El compilador de Java lo ignorará casi por completo (estará en el artefacto compilado pero no tendrá ningún efecto). Una vez dicho esto...

El objetivo de la anotación es describir un contrato que obedecerá el método, lo que ayuda a IDEA a detectar problemas en los métodos que pueden llamar a este método. El contrato en cuestión es un conjunto de cláusulas separadas por punto y coma, cada una de las cuales describe una entrada y una salida que se garantiza que suceda. Causa y efecto están separados por -> , y describen el caso de que cuando proporcionas X al método, siempre se producirá Y. La entrada se describe como una lista separada por comas, que describe el caso de múltiples entradas.

Las entradas posibles son _ (cualquier valor), null !null (no-nulo), false y true , y las posibles salidas fail agregan a esta lista.

Entonces, por ejemplo, null -> false significa que, si se proporciona una entrada null , el resultado es un false booleano. null -> false; !null -> true null -> false; !null -> true expande esto para decir que null siempre devolverá false y un valor no null siempre devolverá true, etc. Finalmente, null -> fail significa que el método emitirá una excepción si le pasa un valor nulo.

Para un ejemplo de múltiples argumentos, null, !null -> fail significa que, en un método de dos argumentos, si el primer argumento es nulo y el segundo no es nulo, se garantizará una excepción.

Si el método no cambia el estado del objeto, sino que simplemente devuelve un nuevo valor, debe establecer pure a verdadero.


La documentación oficial especifica la gramática formal de todos los valores admitidos y reconocidos para la anotación.

En términos simples:

  • Un contrato puede tener 1 o más cláusulas asociadas a él
  • Una cláusula es siempre [args] -> [effect]
  • Args son 1 o más restricciones, que se definen como any | null | !null | false | true any | null | !null | false | true
  • Los efectos son solo una restricción o fail

Veamos un ejemplo rápido: uno de mis favoritos es "Lo que pase con este método, generará una excepción".

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

Aquí, estamos usando _ , o "cualquiera", para indicar que independientemente de lo que pasemos en este método, lanzaremos una excepción.

¿Qué pasa si mentimos y dijimos que este método iba a volverse true sin condiciones?

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

IntelliJ levanta algunas advertencias al respecto.

También está (obviamente) molesto que dijéramos que estábamos devolviendo un booleano cuando estamos en un método nulo ...

Los principales momentos en los que querrá usar @Contract es cuando:

  • Desea garantizar que devuelve verdadero o falso
  • Desea garantizar que devuelve un valor no nulo dadas las restricciones
  • Desea dejar claro que puede devolver un valor nulo dadas las restricciones
  • Desea aclarar que lanzará una excepción dadas las restricciones

Eso no quiere decir que @Contract sea ​​perfecto; lejos de ahi. No puede hacer un análisis muy profundo en ciertos contextos, pero tener esto en su base de código le permite a su herramienta hacer este tipo de análisis de forma gratuita.