Utilizo excepciones para detectar problemas temprano. Por ejemplo:
public int getAverageAge(Person p1, Person p2){
if(p1 == null || p2 == null)
throw new IllegalArgumentException("One or more of input persons is null").
return (p1.getAge() + p2.getAge()) / 2;
}
Mi programa nunca debería pasar null
en esta función. Nunca tengo la intención de hacerlo. Sin embargo, como todos sabemos, suceden cosas involuntarias en la programación.
Lanzar una excepción si se produce este problema, me permite detectarlo y solucionarlo, antes de que cause más problemas en otros lugares del programa. La excepción detiene el programa y me dice "aquí pasaron cosas malas, arréglenlo". En lugar de que esto se null
mueva, el programa causa problemas en otros lugares.
Ahora, tiene razón, en este caso null
simplemente causaría un NullPointerException
inmediato, por lo que podría no ser el mejor ejemplo.
Pero considere un método como este, por ejemplo:
public void registerPerson(Person person){
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
En este caso, a null
como parámetro se pasaría por el programa y podría causar errores mucho más tarde, lo que será difícil de rastrear hasta su origen.
Cambiando la función así:
public void registerPerson(Person person){
if(person == null) throw new IllegalArgumentException("Input person is null.");
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Me permite detectar el problema mucho antes de que cause errores extraños en otros lugares.
Además, una null
referencia como parámetro es solo un ejemplo. Podría haber muchos tipos de problemas, desde argumentos inválidos hasta cualquier otra cosa. Siempre es mejor detectarlos temprano.
Entonces mi pregunta es simple: ¿es esta una buena práctica? ¿Es bueno el uso de excepciones como herramientas para prevenir problemas? ¿Es esta una aplicación legítima de excepciones o es problemática?
fuente
Respuestas:
Sí, "fallar temprano" es un principio muy bueno, y esta es simplemente una forma posible de implementarlo. Y en los métodos que tienen que devolver un valor específico, en realidad no hay mucho más que pueda hacer para fallar deliberadamente: lanzar excepciones o desencadenar afirmaciones. Se supone que las excepciones señalan condiciones 'excepcionales', y la detección de un error de programación ciertamente es excepcional.
fuente
Sí, lanzar excepciones es una buena idea. Tírelos temprano, tírelos a menudo, tírelos con entusiasmo.
Sé que hay un debate de "excepciones versus aserciones", con algunos tipos de comportamiento excepcional (específicamente, aquellos que se piensa que reflejan errores de programación) manejados por aserciones que pueden "compilarse" para el tiempo de ejecución en lugar de depurar / probar compilaciones. Pero la cantidad de rendimiento que se consume en algunas comprobaciones de corrección adicionales es mínima en el hardware moderno, y cualquier costo adicional es ampliamente compensado por el valor de tener resultados correctos e incorruptos. En realidad, nunca conocí una base de código de aplicación para la cual quisiera que se eliminaran (la mayoría) las verificaciones en tiempo de ejecución.
Estoy tentado a decir que no querría muchas comprobaciones adicionales y condicionales dentro de los lazos estrechos del código numéricamente intensivo ... pero en realidad es donde se generan muchos errores numéricos, y si no se detectan allí, se propagarán hacia afuera para efectuar todos los resultados. Entonces, incluso allí, vale la pena hacer los cheques. De hecho, algunos de los mejores y más eficaces algoritmos numéricos se basan en la evaluación de errores.
Un último lugar para ser muy consciente del código adicional es el código sensible a la latencia, donde los condicionales adicionales pueden causar paradas en la tubería. Entonces, en medio del sistema operativo, DBMS y otros núcleos de middleware, y manejo de protocolo / comunicación de bajo nivel. Pero, de nuevo, esos son algunos de los lugares en los que es más probable que se observen errores, y sus efectos (seguridad, corrección e integridad de los datos) son los más dañinos.
Una mejora que he encontrado es no lanzar solo excepciones de nivel base.
IllegalArgumentException
es bueno, pero puede provenir esencialmente de cualquier parte. No se necesita mucho en la mayoría de los idiomas para agregar excepciones personalizadas. Para su módulo de manejo de personas, diga:Entonces cuando alguien ve un
PersonArgumentException
, está claro de dónde viene. Hay un acto de equilibrio sobre cuántas excepciones personalizadas desea agregar, porque no desea multiplicar entidades innecesariamente (Navaja de Occam). A menudo, solo unas pocas excepciones personalizadas son suficientes para indicar "¡este módulo no está obteniendo los datos correctos!" o "¡este módulo no puede hacer lo que se supone que debe hacer!" de una manera específica y personalizada, pero no tan precisa como para tener que volver a implementar toda la jerarquía de excepciones. A menudo llego a ese pequeño conjunto de excepciones personalizadas comenzando con excepciones de stock, luego escaneando el código y dándome cuenta de que "estos N lugares están generando excepciones de stock, pero se reducen a la idea de nivel superior de que no están obteniendo los datos ellos necesitanfuente
I've never actually met an application codebase for which I'd want (most) checks removed at runtime.
Entonces no has hecho código que sea crítico para el rendimiento. Estoy trabajando en algo en este momento que hace 37 millones de operaciones por segundo con afirmaciones compiladas y 42 millones sin ellas. Las aserciones no están allí para validar la entrada externa, están ahí para asegurarse de que el código sea correcto. Mis clientes están más que felices de tomar el aumento del 13% una vez que estoy satisfecho de que mis cosas no están rotas.PersonArgumentException
no es tan claro comoIllegalArgumentException
. Se sabe universalmente que este último se lanza cuando se pasa un argumento ilegal. De hecho, esperaría que se lanzara el primero si unPerson
estaba en un estado no válido para una llamada (Similar aInvalidOperationException
en C #).Fallar lo antes posible es excelente al depurar una aplicación. Recuerdo una falla de segmentación particular en un programa heredado de C ++: el lugar donde se detectó el error no tenía nada que ver con el lugar donde se introdujo (el puntero nulo se movió felizmente de un lugar a otro en la memoria antes de que finalmente causara un problema ) Los rastros de pila no pueden ayudarte en esos casos.
Entonces, sí, la programación defensiva es un enfoque realmente efectivo para detectar y corregir errores rápidamente. Por otro lado, puede exagerarse, especialmente con referencias nulas.
En su caso específico, por ejemplo: si alguna de las referencias es nula, se
NullReferenceException
arrojará en la siguiente declaración, cuando intente obtener la edad de una persona. Realmente no necesita verificar las cosas usted mismo aquí: deje que el sistema subyacente detecte esos errores y arroje excepciones, por eso existen .Para un ejemplo más realista, puede usar
assert
declaraciones, que:Son más cortos de escribir y leer:
Están diseñados específicamente para su enfoque. En un mundo donde tiene tanto afirmaciones como excepciones, puede distinguirlas de la siguiente manera:
Por lo tanto, exponer sus suposiciones sobre las entradas y / o el estado de su aplicación con aserciones le permite al próximo desarrollador comprender un poco más el propósito de su código.
Un analizador estático (por ejemplo, el compilador) también podría ser más feliz.
Finalmente, las aserciones se pueden eliminar de la aplicación implementada utilizando un solo conmutador. Pero en términos generales, no espere mejorar la eficiencia con eso: las verificaciones de afirmaciones en tiempo de ejecución son insignificantes.
fuente
Hasta donde sé, diferentes programadores prefieren una solución u otra.
La primera solución generalmente se prefiere porque es más concisa, en particular, no tiene que verificar la misma condición una y otra vez en diferentes funciones.
Encuentro la segunda solución, por ejemplo
más sólido porque
registerPerson()
se llama, no cuando se lanza una excepción de puntero nulo en algún lugar de la pila de llamadas. La depuración se vuelve mucho más fácil: todos sabemos qué tan lejos puede viajar un valor no válido a través del código antes de que se manifieste como un error.registerPerson()
no hace suposiciones sobre qué otras funciones terminarán usando elperson
argumento y cómo lo usarán: la decisión quenull
es un error se toma e implementa localmente.Entonces, especialmente si el código es bastante complejo, tiendo a preferir este segundo enfoque.
fuente
En general, sí, es una buena idea "fallar temprano". Sin embargo, en su ejemplo específico, lo explícito
IllegalArgumentException
no proporciona una mejora significativa sobre unNullReferenceException
- porque ambos objetos operados ya se entregan como argumentos para la función.Pero veamos un ejemplo ligeramente diferente.
Si no hubiera ningún argumento que verificara en el constructor, obtendría un
NullReferenceException
al llamarCalculate
.Pero el código roto no era ni la
Calculate
función ni el consumidor de laCalculate
función. El fragmento de código roto fue el código que intenta construirPersonCalculator
con un valor nuloPerson
, de modo que es donde queremos que ocurra la excepción.Si eliminamos esa comprobación de argumento explícito, tendrá que averiguar por qué
NullReferenceException
ocurrió un error cuandoCalculate
se llamó al. Y rastrear por qué el objeto se construyó con unanull
persona puede ser complicado, especialmente si el código que construye la calculadora no está cerca del código que realmente llama a laCalculate
función.fuente
No en los ejemplos que das.
Como dices, lanzar explícitamente no te gana mucho cuando vas a obtener una excepción poco después. Muchos argumentarán que tener una excepción explícita con un buen mensaje es mejor, aunque no estoy de acuerdo. En escenarios prelanzados, el seguimiento de la pila es lo suficientemente bueno. En escenarios posteriores al lanzamiento, el sitio de la llamada a menudo puede proporcionar mejores mensajes que dentro de la función.
La segunda forma es proporcionar demasiada información a la función. Esa función no necesariamente debe saber que las otras funciones arrojarán una entrada nula. Incluso si lanzan una entrada nula ahora , se vuelve muy problemático refactorizar si eso deja de ser así, ya que la verificación nula se extiende por todo el código.
Pero en general, debe tirar temprano una vez que determine que algo salió mal (respetando DRY). Sin embargo, estos quizás no sean buenos ejemplos de eso.
fuente
En su función de ejemplo, preferiría que no hiciera verificaciones y simplemente permitiera
NullReferenceException
que sucediera.Por un lado, no tiene sentido pasar un nulo allí de todos modos, así que resolveré el problema de inmediato basándome en el lanzamiento de a
NullReferenceException
.Dos, es que si cada función arroja excepciones ligeramente diferentes en función de qué tipo de entradas obviamente incorrectas se han proporcionado, entonces muy pronto tendrá funciones que podrían arrojar 18 tipos diferentes de excepciones, y muy pronto se encontrará diciendo que eso también mucho trabajo con el que lidiar, y simplemente suprimir todas las excepciones de todos modos.
No hay nada que pueda hacer realmente para ayudar a la situación de un error en tiempo de diseño en su función, así que deje que ocurra la falla sin cambiarla.
fuente
RuntimeException
no es necesariamente una suposición válida.