Me pregunto si debería defenderme contra el valor de retorno de una llamada al método al validar que cumplan mis expectativas, incluso si sé que el método al que llamo cumplirá tales expectativas.
DADO
User getUser(Int id)
{
User temp = new User(id);
temp.setName("John");
return temp;
}
DEBERIA HACER
void myMethod()
{
User user = getUser(1234);
System.out.println(user.getName());
}
O
void myMethod()
{
User user = getUser(1234);
// Validating
Preconditions.checkNotNull(user, "User can not be null.");
Preconditions.checkNotNull(user.getName(), "User's name can not be null.");
System.out.println(user.getName());
}
Estoy preguntando esto a nivel conceptual. Si conozco el funcionamiento interno del método que estoy llamando. Ya sea porque lo escribí o lo inspeccioné. Y la lógica de los posibles valores que devuelve cumple mis condiciones previas. ¿Es "mejor" o "más apropiado" omitir la validación, o aún debería defenderme de los valores incorrectos antes de continuar con el método que estoy implementando actualmente, incluso si siempre debe pasar?
Mi conclusión de todas las respuestas (siéntase libre de responder):
Afirmar cuando- El método ha demostrado comportarse mal en el pasado
- El método es de una fuente no confiable
- El método se usa desde otros lugares y no establece explícitamente sus condiciones posteriores.
- El método está muy cerca del suyo (vea la respuesta elegida para más detalles)
- El método define explícitamente su contrato con algo como el documento adecuado, la seguridad de tipo, una prueba unitaria o una verificación posterior a la condición
- El rendimiento es crítico (en cuyo caso, una afirmación de modo de depuración podría funcionar como un enfoque híbrido)
java
validation
null
return-type
defensive-programming
Didier A.
fuente
fuente
Null
)? Probablemente sea igualmente problemático si el nombre es""
(no nulo pero la cadena vacía) o"N/A"
. O puedes confiar en el resultado, o tienes que ser apropiadamente paranoico.Respuestas:
Eso depende de la probabilidad de que getUser y myMethod cambien y, lo que es más importante, de la probabilidad de que cambien independientemente uno del otro .
Si de alguna manera sabe con certeza que getUser nunca cambiará en el futuro, entonces sí, es una pérdida de tiempo validarlo, tanto como perder el tiempo de validación que
i
tiene un valor de 3 inmediatamente después de unai = 3;
declaración. En realidad, no lo sabes. Pero hay algunas cosas que sí sabes:¿Estas dos funciones "viven juntas"? En otras palabras, ¿tienen las mismas personas que los mantienen, son parte del mismo archivo y, por lo tanto, es probable que se mantengan "sincronizados" por sí mismos? En este caso, probablemente sea excesivo agregar verificaciones de validación, ya que eso simplemente significa más código que tiene que cambiar (y potencialmente generar errores) cada vez que las dos funciones cambian o se refactorizan en un número diferente de funciones.
¿La función getUser es parte de una API documentada con un contrato específico, y myMethod es simplemente un cliente de dicha API en otra base de código? Si es así, puede leer esa documentación para averiguar si debe validar los valores de retorno (o validar previamente los parámetros de entrada) o si realmente es seguro seguir ciegamente el camino feliz. Si la documentación no lo aclara, pídale al encargado que lo arregle.
Finalmente, si esta función en particular ha cambiado repentina e inesperadamente su comportamiento en el pasado, de una manera que rompió su código, tiene todo el derecho de ser paranoico al respecto. Los errores tienden a agruparse.
Tenga en cuenta que todo lo anterior se aplica incluso si usted es el autor original de ambas funciones. No sabemos si se espera que estas dos funciones "vivan juntas" por el resto de sus vidas, o si se separarán lentamente en módulos separados, o si de alguna manera te has disparado en el pie con un error en una versión anterior versiones de getUser. Pero probablemente puedas hacer una suposición bastante decente.
fuente
getUser
tuviera que cambiar más tarde, para que pudiera volver nulo, ¿la verificación explícita le daría información que no podría obtener de "NullPointerException" y el número de línea?La respuesta de Ixrec es buena, pero adoptaré un enfoque diferente porque creo que vale la pena considerarlo. A los fines de esta discusión, hablaré sobre afirmaciones, ya que ese es el nombre con el que
Preconditions.checkNotNull()
tradicionalmente se conoce.Lo que me gustaría sugerir es que los programadores a menudo sobreestiman el grado en que están seguros de que algo se comportará de cierta manera y subestiman las consecuencias de que no se comporte de esa manera. Además, los programadores a menudo no se dan cuenta del poder de las afirmaciones como documentación. Posteriormente, en mi experiencia, los programadores afirman las cosas con mucha menos frecuencia de lo que deberían.
Mi lema es:
Naturalmente, si está absolutamente seguro de que algo se comporta de cierta manera, se abstendrá de afirmar que de hecho se comportó de esa manera, y eso es en su mayor parte razonable. Realmente no es posible escribir software si no puedes confiar en nada.
Pero hay excepciones incluso a esta regla. Curiosamente, para tomar el ejemplo de Ixrec, existe un tipo de situación en la que es perfectamente deseable seguir
int i = 3;
con una afirmación quei
esté dentro de cierto rango. Esta es la situación en la quei
es probable que sea alterada por alguien que está probando varios escenarios hipotéticos, y el código que sigue depende dei
tener un valor dentro de un cierto rango, y no es inmediatamente obvio mirando rápidamente el siguiente código. El rango aceptable es. Entonces,int i = 3; assert i >= 0 && i < 5;
al principio puede parecer absurdo, pero si lo piensas, te dice que puedes jugari
y también te dice el rango dentro del cual debes quedarte. Una afirmación es el mejor tipo de documentación, porquela máquina lo aplica, por lo que es una garantía perfecta y se refactoriza junto con el código , por lo que sigue siendo pertinente.Su
getUser(int id)
ejemplo no es un pariente conceptual muy distante delassign-constant-and-assert-in-range
ejemplo. Verá, por definición, los errores ocurren cuando las cosas exhiben un comportamiento que es diferente del comportamiento que esperábamos. Es cierto que no podemos afirmar todo, por lo que a veces habrá alguna depuración. Pero es un hecho bien establecido en la industria que cuanto más rápido detectamos un error, menos cuesta. Las preguntas que debe hacer no son solo qué tan seguro está de quegetUser()
nunca devolverá nulo (y nunca devolverá a un usuario con un nombre nulo), sino también, qué tipo de cosas malas sucederán con el resto del código si lo hace en De hecho, un día devuelve algo inesperado, y lo fácil que es mirar rápidamente el resto del código para saber con precisión lo que se esperabagetUser()
.Si el comportamiento inesperado hace que su base de datos se corrompa, entonces tal vez debería afirmar incluso si está 101% seguro de que no sucederá. Si un
null
nombre de usuario causara un extraño error críptico imposible de rastrear miles de líneas más abajo y miles de millones de ciclos de reloj más tarde, entonces quizás sea mejor fallar lo antes posible.Por lo tanto, aunque no estoy en desacuerdo con la respuesta de Ixrec, te sugiero que consideres seriamente el hecho de que las afirmaciones no cuestan nada, por lo que realmente no hay nada que perder, solo que ganar, al usarlas libremente.
fuente
... && sureness < 1)
.Un definitivo no.
Una persona que llama nunca debe verificar si la función a la que llama respeta su contrato. La razón es simple: potencialmente hay muchas personas que llaman y es poco práctico y posiblemente incluso erróneo desde un punto de vista conceptual para cada persona que llama duplicar la lógica para verificar los resultados de otras funciones.
Si se van a realizar verificaciones, cada función debe verificar sus propias condiciones posteriores. Las condiciones posteriores le dan la confianza de que implementó la función correctamente y los usuarios podrán confiar en el contrato de su función.
Si una determinada función no está destinada a devolver un valor nulo, esto debe documentarse y formar parte del contrato de esa función. Este es el objetivo de estos contratos: ofrecer a los usuarios algunos invariantes en los que puedan confiar para que enfrenten menos obstáculos al escribir sus propias funciones.
fuente
Si nulo es un valor de retorno válido del método getUser, entonces su código debe manejar eso con un If, no una excepción; no es un caso excepcional.
Si nulo no es un valor de retorno válido del método getUser, entonces hay un error en getUser: el método getUser debería arrojar una excepción o, de lo contrario, corregirse.
Si el método getUser es parte de una biblioteca externa o no se puede cambiar y desea que se generen excepciones en el caso de un retorno nulo, ajuste la clase y el método para realizar las comprobaciones y arroje la excepción para que cada instancia de la llamada a getUser es consistente en su código.
fuente
Yo diría que la premisa de su pregunta es incorrecta: ¿por qué usar una referencia nula para comenzar?
El patrón de objeto nulo es una forma de devolver objetos que esencialmente no hacen nada: en lugar de devolver nulo para su usuario, devuelva un objeto que representa "ningún usuario".
Este usuario estaría inactivo, tendría un nombre en blanco y otros valores no nulos que indicarían "nada aquí". El beneficio principal es que no necesita tirar basura, su programa anulará cheques, registros, etc., simplemente use sus objetos.
¿Por qué un algoritmo debería preocuparse por un valor nulo? Los pasos deben ser los mismos. Es igual de fácil y mucho más claro tener un objeto nulo que devuelva cero, cadena en blanco, etc.
Aquí hay un ejemplo de comprobación de código para nulo:
Aquí hay un ejemplo de código que usa un objeto nulo:
¿Cuál es más claro? Creo que el segundo es.
Si bien la pregunta está etiquetada como Java , vale la pena señalar que el patrón de objeto nulo es realmente útil en C ++ cuando se usan referencias. Las referencias de Java son más parecidas a los punteros de C ++ y permiten valores nulos. Las referencias de C ++ no pueden sostenerse
nullptr
. Si a uno le gustaría tener algo análogo a una referencia nula en C ++, el patrón de objeto nulo es la única forma que conozco para lograrlo.fuente
getCount()
documenta una garantía de que ahora no devuelve 0, y no lo hará en el futuro, excepto en circunstancias en las que alguien decida hacer un cambio importante y busque actualizar todo lo que llama para hacer frente a La nueva interfaz. Ver lo que hace el código actualmente no es de ayuda, pero si va a inspeccionar el código, entonces el código a inspeccionar es la unidad de pruebagetCount()
. Si prueban que no devuelve 0, entonces sabe que cualquier cambio futuro para devolver 0 se considerará un cambio importante porque las pruebas fallarán.En general, diría que depende principalmente de los siguientes tres aspectos:
robustez: ¿puede el método de llamada hacer frente al
null
valor? Si unnull
valor pudiera resultar en unRuntimeException
, recomendaría una verificación, a menos que la complejidad sea baja y los métodos llamados / invocadores sean creados por el mismo autor ynull
no se esperen (por ejemplo, ambosprivate
en el mismo paquete).responsabilidad del código: si el desarrollador A es responsable del
getUser()
método y el desarrollador B lo usa (por ejemplo, como parte de una biblioteca), recomiendo encarecidamente validar su valor. Solo porque el desarrollador B podría no saber acerca de un cambio que resulte en un posiblenull
valor de retorno.complejidad: cuanto mayor es la complejidad general del programa o entorno, más recomiendo validar el valor de retorno. Incluso si está seguro de que no puede ser
null
hoy en el contexto del método de llamada, puede ser que tenga que cambiargetUser()
por otro caso de uso. Una vez que pasen algunos meses o años y se hayan agregado miles de líneas de códigos, esto puede ser una trampa.Además, recomendaría documentar los posibles
null
valores de retorno en un comentario JavaDoc. Debido al resaltado de las descripciones de JavaDoc en la mayoría de los IDE, esto puede ser una advertencia útil para quien lo usegetUser()
.fuente
Definitivamente no tienes que hacerlo.
Por ejemplo, si ya sabe que un retorno nunca puede ser nulo, ¿por qué querría agregar un cheque nulo? Agregar un cheque nulo no va a romper nada, pero es redundante. Y mejor no codifique la lógica redundante siempre que pueda evitarla.
fuente