¿Está afirmado Java roto?
language-features assert (12)
Mientras revisaba las preguntas, recientemente descubrí la palabra clave assert
en Java. Al principio, estaba emocionado. Algo útil que no sabía ya! ¡Una forma más eficiente para que compruebe la validez de los parámetros de entrada! Yay aprendiendo!
Pero luego lo miré más de cerca, y mi entusiasmo no fue tan "atemperado" como "completamente apagado" por un simple hecho: puedes desactivar las aserciones. *
Esto suena como una pesadilla. Si estoy afirmando que no quiero que el código continúe si la entrada listOfStuff
es null
, ¿por qué motivo querría que se ignore esa afirmación? Parece que si estoy depurando una parte del código de producción y sospecho que listOfStuff
pudo haberse pasado erróneamente un null
pero no veo ninguna evidencia de archivo de registro de esa afirmación, no puedo confiar en que listOfStuff
haya recibido un listOfStuff
válido. valor; También debo tener en cuenta la posibilidad de que las afirmaciones se hayan desactivado por completo.
Y esto supone que yo soy el que está depurando el código. Alguien que no esté familiarizado con las aserciones puede ver eso y asumir (bastante razonablemente) que si el mensaje de la aserción no aparece en el registro, listOfStuff
no podría ser el problema. Si su primer encuentro con assert
fue en la naturaleza, ¿se le ocurriría que pudiera desactivarlo por completo? No es que haya una opción de línea de comandos que te permita desactivar los bloques try / catch, después de todo.
Todo lo cual me lleva a mi pregunta (¡y esta es una pregunta, no una excusa para una perorata! ¡Lo prometo!):
¿Qué me estoy perdiendo?
¿Hay algún matiz que hace que la implementación de asert de Java sea mucho más útil de lo que le doy crédito? ¿Es la capacidad de habilitarlo / deshabilitarlo desde la línea de comandos realmente increíble en algunos contextos? ¿Lo malinterpreto de alguna manera cuando imagino usarlo en código de producción en lugar de declaraciones como if (listOfStuff == null) barf();
?
Siento que hay algo importante aquí que no entiendo.
* Bueno, técnicamente hablando, en realidad están desactivados por defecto; Tienes que hacer todo lo posible para encenderlos. Pero aún así, puedes eliminarlos por completo.
Edición: Iluminación solicitada, iluminación recibida. La noción de que assert
es ante todo una herramienta de depuración va un largo, largo camino para que tenga sentido para mí.
Todavía no estoy de acuerdo con la idea de que las comprobaciones de entrada para métodos privados no triviales deben deshabilitarse en un entorno de producción porque el desarrollador cree que las entradas incorrectas son imposibles. En mi experiencia, el código de producción maduro es una cosa enloquecida y en expansión, desarrollada a lo largo de los años por personas con diversos grados de habilidad dirigidos a los requisitos rápidamente cambiantes de diversos grados de cordura. E incluso si la mala entrada es realmente imposible, una pieza de mantenimiento descuidado que codifica dentro de seis meses puede cambiar eso. El enlace gustafc proporcionado (gracias!) Incluye esto como un ejemplo:
assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;
Deshabilitar un control tan simple en la producción me parece estúpidamente optimista. Sin embargo, esta es una diferencia en la filosofía de codificación, no una característica rota.
Además, definitivamente puedo ver el valor de algo como esto:
assert reallyExpensiveSanityCheck(someObject) : someObject;
Gracias a todos los que se tomaron el tiempo para ayudarme a entender esta característica; Es muy apreciado.
Creo que es la forma en que se interpreta y visualiza el uso de aserciones.
Si realmente desea agregar la verificación en su código de producción real, ¿por qué no usar If directamente o cualquier otra declaración condicional?
Aquellos que ya estaban presentes en el lenguaje, la idea de afirmar era solo para que el desarrollador agregara afirmaciones solo si realmente no esperan que esta condición suceda.
Por ejemplo, verificando que un objeto sea nulo, digamos que un desarrollador escribió un método privado y lo llamó desde dos lugares (este no es un ejemplo ideal pero puede funcionar para métodos privados) en la clase donde sabe que pasa un objeto no nulo, en lugar de agregando una verificación innecesaria de si a partir de hoy usted sabe que no hay forma de que el objeto sea nulo. Pero si alguien mañana llama a este método con un argumento nulo, en la prueba de la unidad del desarrollador esto puede ser detectado debido a la presencia de una afirmación y en el código final todavía no puede. No necesito un cheque si.
Esto no responde directamente a su pregunta sobre la assert
, pero le recomiendo que revise la clase de condiciones Preconditions en guava / google-collections . Te permite escribir cosas bonitas como esta (usando importaciones estáticas):
// throw NPE if listOfStuff is null
this.listOfStuff = checkNotNull(listOfStuff);
// same, but the NPE will have "listOfStuff" as its message
this.listOfStuff = checkNotNull(listOfStuff, "listOfStuff");
Parece que algo como esto podría ser lo que quieres (y no se puede apagar).
Esto suena bien. Las aserciones son solo una herramienta que es útil para depurar el código; no deben activarse todo el tiempo, especialmente en el código de producción.
Por ejemplo, en C o C ++, las aserciones están deshabilitadas en las versiones de lanzamiento.
Las afirmaciones de Java no se hacen realmente para la validación de argumentos: se establece específicamente que las aserciones no se deben usar en lugar de la antigua IllegalArgumentException
(y tampoco es así como se usan en los lenguajes C-ish). Están más ahí para la validación interna, para permitirle hacer una suposición sobre el código que no es obvio al mirarlo.
En cuanto a desactivarlas, también lo hace en C (++), solo que si alguien tiene una versión sin aserciones, no tiene forma de encenderla. En Java, simplemente reinicia la aplicación con los parámetros de VM adecuados.
Las afirmaciones están destinadas a garantizar que se cumplan realmente las cosas de las que está seguro que cumple su código. Es una ayuda en la depuración, en la fase de desarrollo del producto, y generalmente se omite cuando se libera el código.
¿Qué me estoy perdiendo?
No estás utilizando afirmaciones de la forma en que estaban destinadas a ser utilizadas. Usted dijo "verificar la validez de los parámetros de entrada", que es precisamente el tipo de cosas que no desea verificar con aserciones.
La idea es que si una afirmación falla, el 100% tiene un error en su código. Las aserciones se utilizan a menudo para identificar el error antes de lo que habría surgido de otra manera.
Las afirmaciones no son para que el usuario final las vea. Son para el programador, por lo que puede asegurarse de que el código esté haciendo lo correcto mientras se está desarrollando. Una vez que se realizan las pruebas, las aserciones generalmente se desactivan por razones de rendimiento.
Si está anticipando que algo malo va a suceder en la producción, como que listOfStuff sea nulo, entonces su código no se ha probado lo suficiente o no está limpiando su entrada antes de dejar que lo haga. De cualquier manera, un "si (cosas malas) {lanzar una excepción}" sería mejor. Las afirmaciones son para tiempo de prueba / desarrollo, no para producción.
Las afirmaciones son para indicar un problema en el código que puede ser recuperable, o como una ayuda en la depuración. Debe usar un mecanismo más destructivo para los errores más graves, como detener el programa.
También se pueden usar para detectar un error irrecuperable antes de que la aplicación falle más tarde en los escenarios de depuración y prueba para ayudarlo a reducir un problema. Parte de la razón de esto es que la comprobación de integridad no reduce el rendimiento del código bien probado en producción.
Además, en ciertos casos, como una fuga de recursos, la situación puede no ser deseable, pero las consecuencias de detener el programa son peores que las consecuencias de continuar.
Las afirmaciones son realmente una herramienta de documentación excelente y concisa para un mantenedor de código.
Por ejemplo puedo escribir:
foo debe ser no nulo y mayor que 0
O poner esto en el cuerpo del programa:
assert foo != null;
assert foo.value > 0;
Son extremadamente valiosos para documentar métodos privados / paquetes privados para expresar invariantes originales del programador.
Para el bono adicional, cuando el subsistema comienza a comportarse de manera inestable, puede activar las aserciones y agregar una validación adicional al instante.
Si las afirmaciones no se pueden desactivar, entonces ¿por qué deberían existir?
Si desea realizar una verificación de validez en una entrada, puede escribir fácilmente
if (foobar<=0) throw new BadFoobarException();
o abrir un cuadro de mensaje o lo que sea útil en el contexto.
El punto central de las afirmaciones es que son algo que se puede activar para la depuración y desactivar para la producción.
Todos los idiomas que he visto con aserciones vienen con la capacidad de apagarlos. Cuando escribes una afirmación debes pensar que "esto es una tontería, no hay forma en el universo de que esto pueda ser falso": si crees que podría ser falso, debería tratarse de un error. La afirmación es solo para ayudarlo durante el desarrollo si algo sale terriblemente mal; cuando construye el código para la producción, los desactiva para ahorrar tiempo y evitar (con suerte) verificaciones superfluas
Use una afirmación si está dispuesto a pagar $ 1 a su usuario final siempre que la aseveración falle.
Un fallo de aserción debe ser una indicación de un error de diseño en el programa.
Una afirmación indica que he diseñado el programa de tal manera que conozco y garantizo que el predicado especificado siempre se cumple.
Una afirmación es útil para los lectores de mi código, ya que ven que (1) estoy dispuesto a poner algo de dinero en esa propiedad; y (2) en ejecuciones previas y casos de prueba, la propiedad se mantuvo efectivamente.
Mi apuesta asume que el cliente de mi código se atiene a las reglas y se adhiere al contrato que él y yo acordamos. Este contrato puede ser tolerante (todos los valores de entrada permitidos y verificados para su validez) o exigente (el cliente y yo acordamos que nunca suministrará ciertos valores de entrada [descritos como condiciones previas], y que no quiere que verifique estos valores una y otra vez). Si el cliente se atiene a las reglas, y mis aseveraciones fallan, el cliente tiene derecho a alguna compensación.
assert
es una pieza útil de Diseño por Contrato . En ese contexto, las afirmaciones se pueden utilizar en:
- Comprobaciones previas.
- Controles de postcondición.
- Comprobaciones de resultados intermedios.
- Cheques invariantes de clase.
Las afirmaciones pueden ser costosas de evaluar (tome, por ejemplo, la clase invariante, que debe contener antes y después de llamar a cualquier método público de su clase). Normalmente, las aserciones solo se desean en construcciones de depuración y para propósitos de prueba; Usted afirma cosas que no pueden suceder, cosas que son sinónimo de tener un error. Las aserciones verifican su código contra su propia semántica.
Las aserciones no son un mecanismo de validación de entrada. Cuando la entrada realmente puede ser correcta o incorrecta en el entorno de producción, es decir, para las capas de entrada-salida, utilice otros métodos, como excepciones o buenas comprobaciones condicionales antiguas.