2010-07-11 22 views
8

Me pregunto acerca del costo de usar una prueba/excepción para manejar valores nulos en comparación con el uso de una instrucción if para verificar nulos primero.Java: compruebe la nulidad o permita el manejo de excepciones

Para proporcionar más información. Hay un> 50% de posibilidades de obtener nulos, porque en esta aplicación. es común tener un valor nulo si no se han ingresado datos ... por lo que intentar un cálculo utilizando un valor nulo es algo común.

Dicho esto, ¿mejoraría el rendimiento si utilizo una instrucción if para verificar el nulo primero antes del cálculo y simplemente no intento el cálculo en primer lugar, o es menos costoso dejar que se produzca la excepción y manejarlo? ?

gracias por todas las sugerencias :-)

Gracias por un gran pensamiento que provoca la regeneración! Aquí está un ejemplo de pseudocódigo para aclarar la pregunta original:

BigDecimal value1 = null //assume value1 came from DB as null 
BigDecimal divisor = new BigDecimal("2.0"); 

try{ 
    if(value1 != null){ //does this enhance performance?... >50% chance that value1 WILL be null 
     value1.divide(divisor); 
    } 
} 
catch (Exception e){ 
    //process, log etc. the exception 
    //do this EVERYTIME I get null... or use an if statement 
    //to capture other exceptions. 
} 
+2

ack ... no atrapar Excepción ... ¿y si el divisor es CERO? Luego también tomará la ArithmeticException de la división por cero, pero la tratará como la excepción de puntero Null "esperada". Sea tan específico acerca de las excepciones que desea atrapar como sea posible. – TofuBeer

+0

¿Alguien quiere sacar el cuello y decir que una declaración if es menos costosa que una excepción lanzada? – AJay

+0

Sí, lo hago. Una declaración 'if' es * mucho * menos costosa que una excepción lanzada. – EJP

Respuesta

11

lo recomiendo la comprobación de nulos y no hacer el cálculo en lugar de lanzar una excepción.

Una excepción debe ser "excepcional" y rara, no una forma de gestionar el flujo de control.

También sugiero que establezca un contrato con sus clientes con respecto a los parámetros de entrada. Si los valores nulos están permitidos deletérelo; si no lo están, deje en claro lo que se debe pasar, los valores predeterminados y lo que promete devolver si se pasa un valor nulo.

+0

Absolutamente verdaderamente – odiszapc

3

Si hay un> 50% de posibilidades de obtener un valor nulo, ¿no es una excepción?

Personalmente, yo diría que si espera que ocurra algo, debe codificarlo apropiadamente, en este caso, verificando nulo y haciendo lo que sea apropiado. Siempre he entendido que lanzar una excepción no es excesivamente barato, pero no podría decirlo con certeza.

7

Si pasa null argumento es un caso excepcional , entonces me tiro un NullPointerException.

public Result calculate(Input input) { 
    if (input == null) throw new NullPointerException("input"); 
    // ... 
} 

Si pasa null es un caso permitido, entonces me olvidaría el cálculo y, finalmente, volver null. Pero eso, en mi opinión, tiene menos sentido. Pasar null en primera instancia parecería un error en el código de llamada.

De cualquier forma que elija, debe documentarse adecuadamente en el Javadoc para evitar sorpresas para el usuario de la API.

+1

En este caso, si no ha documentado que es aceptable que 'input' se suministre como nulo, lanzaría' IllegalArgumentException' en lugar de 'NullPointerException'. Interpreto 'NullPointerException' para que sea análogo a un error de segmentación: algo que el tiempo de ejecución del host detecta como un error de programación, no algo utilizado por la aplicación en sí. Contra mi propio consejo, la documentación de JDK dice: "Las aplicaciones deben arrojar instancias de esta clase para indicar otros usos ilegales del objeto nulo". http://java.sun.com/javase/7/docs/api/java/lang/NullPointerException.html – seh

+1

@seh: _Effective Java 2nd Edition_ afirma que 'NullPointerException' es la convención para el argumento' null' ilegal. – polygenelubricants

+2

@seh: 'IllegalArgumentException' solo es aplicable para un argumento ilegal que no es' nulo'. Editar: como @poly dijo, sí :) Echa un vistazo en las API de Java SE/EE, verás que esta convención se aplica también allí. – BalusC

3

Probar y atrapar son casi "gratis", pero los tiros pueden ser muy costosos. Normalmente, los creadores de VM no optimizan las rutas de excepción ya que, bueno, son excepcionales (se supone que son raras).

NullPointerException indica un error del programador (RuntimeException) y no debe ser capturado. En lugar de capturar la NullPointerException, debe corregir su código para que la excepción no se produzca en primer lugar.

Peor aún, si detecta NullPointerException y una parte diferente del código de cálculo arroja NullPointerException que la que espera arrojar, ahora ha enmascarado un error.

Para responder completamente a su pregunta, lo implementaría de una manera, lo perfilaría, y luego lo implementaría de otra manera y lo perfilaría ... entonces solo usaría el que tiene la declaración if que evita arrojar la NullPointerException independientemente de los cuales es más rápido simplemente por el punto anterior.

0

Estoy de acuerdo con la mayoría de las otras respuestas en que usted debería preferir el cheque nulo al try-catch. Y he votado por algunos de ellos.

Pero debe tratar de evitar la necesidad tanto como sea posible.

Hay un> 50% de posibilidades de obtener nulos, porque en esta aplicación. es común tener un valor nulo si no se han ingresado datos ... por lo que intentar un cálculo utilizando un valor nulo es algo común.

Eso es lo que realmente debería estar fijación.

Cree valores predeterminados razonables que garanticen que el cálculo funciona o evite llamar a un cálculo sin proporcionar los datos de entrada necesarios.

Para muchos de los tipos de datos estándar y cálculos que los involucran hay valores predeterminados razonables. Los números predeterminados son 0 o 1, dependiendo de su significado, las cadenas por defecto y las colecciones para vaciar, y muchos cómputos simplemente funcionan. Para objetos más complejos de su propia creación, considere el patrón Null Object.

+0

Los valores predeterminados en sí mismos pueden terminar siendo un caso de ocultación de errores. No está fuera de límites, pero de manera predeterminada es mejor arrojar excepciones. Los retrocesos se deben aplicar muy raramente y caso por caso, solo cuando realmente tengan sentido e, idealmente, donde sea claro para el usuario, como sí/no, por defecto sí :). Los valores predeterminados deberían ser más convenientes que la corrección de errores. – jgmjgm

0

Si tiene algún caso en el que un resultado o entrada no puede ser manejado por su programa, entonces eso debería ser un error. Debe saber lo que su programa puede manejar y permitir solo eso. En lo que respecta a posibles casos futuros en los que podría manejarse un resultado, pero todavía no, sugeriría considerarlo un error hasta que realmente necesite ese resultado. Si tiene dudas, no sabe, el programa no sabe, no se puede manejar, no ha equipado su programa para manejarlo, así que es un error. El programa no puede hacer nada más que detenerse.

Para la entrada del usuario es una muy mala idea confiar en su programa para eventualmente fallar. No sabe cuándo o incluso si se bloqueará. Podría terminar haciendo lo incorrecto o hacer diez cosas y luego colgarse que no debería haberlo hecho y no lo habría hecho si la entrada hubiera sido validada.

En términos de protección contra sus propios errores que es más de una bolsa mixta. Querrá enfocarse mucho más en asegurarse de que las cosas funcionen probando, eliminando incógnitas, revisando, asegurándose de saber exactamente cómo funciona su programa, etc. De todos modos, ocasionalmente habrá casos en que el procesamiento interno pueda producir resultados no deseados.

Cuando resulta que un resultado no es un error en un caso determinado, no maneja la excepción, maneja nulo, no una excepción. En ese caso, el resultado no es un error. Entonces manejas el resultado y no el error. No podría ser más simple. Si vas a tomar una excepción y luego hacer algo que se puede hacer con un if, tales como:

try 
    extra = i_need_it(extra_id) 
    show('extra', extra) 
catch 
    show('add_extra') 

Entonces eso no es correcto. Tienes un curso de acción perfectamente aceptable si no tienes algo extra.

Esto es mucho mejor y que mantiene su intención clara y sin el nivel de detalle adicional:

Something extra = i_want_it(extra_id) 
if extra ==== null 
    show('add_extra') 
else 
    show('extra', extra) 

Aviso aquí se necesita nada especial para evitar la captura de una excepción de otra capa. Cómo puse try catch arriba es una mala práctica.Sólo se debe envolver la cosa que produce una excepción:

Something extra 
try 
    extra = i_need_it(extra_id) 
if extra === null 
    show('add_extra') 
else 
    show('extra', extra) 

Cuando cosa sobre ella de esa manera, entonces se acaba convirtiendo a excepción nula y luego de vuelta otra vez. Esta es la codificación YoYo.

usted debe comenzar con:

Object i_need_it(int id) throws 

Hasta que no esté en condiciones de aplicar el manejo de nulo. Si puede implementar el manejo de la excepción, puede implementar el manejo del nulo.

Cuando resulta que algo que no siempre es necesaria, ya sea añadir o cambiar este método i_need_it a ella (si es nula siempre se maneja):

Object|null i_want_it(int id) 

Una alternativa es comprobar es que existe en primer lugar:

bool does_it_exist(int id) 

La razón esto no se hace tan a menudo se debe a que por lo general viene a cabo de esta manera:

if(does_it_exist(id)) 
    Something i_need = i_need_it(id) 

Esto tiende a ser más propenso a problemas de concurrencia, puede requerir más llamadas que podrían no ser confiables (IO en la red) y puede ser ineficiente (dos RTT en lugar de uno). Otras llamadas a menudo se fusionan de esta manera, como actualización si existe, insertar si es único, actualizar si existe o insertar, etc. que luego devuelve lo que normalmente sería el resultado de la verificación inicial. Algunos de estos tienen conflictos sobre la eficiencia del tamaño de la carga útil y la eficiencia de RTT, que también pueden variar en función de una serie de factores.

Sin embargo, es más barato cuando necesita un comportamiento alternativo basado en si algo existe o no, pero no necesita trabajar en él. Si no necesita preocuparse por las preocupaciones anteriores, es un poco más claro.

puede que incluso quiere:

void it_must_exist(int id) throws 

puede resultar muy útil porque si sólo necesita asegurarse de algo existe a menudo es más económico que obtenerla. Sin embargo, es raro que lo necesite, ya que en la mayoría de los casos querrá saber si algo existe para trabajar directamente en él.

Una forma de concebir es que no haría 'does_it_exist' lanzar una excepción cuando simplemente puede devolver un booleano explícitamente en la pila de llamadas, 'i_want_it' es un 'has' y 'get' combinados en efecto .

Si bien tener dos métodos separados separa más claramente firmas de métodos, a veces es posible que tenga que pasar por debajo de otra cosa y la forma más sencilla para que si no la mía un poco de ambigüedad es:

Object|null get(int id, bool require) throws 

Esto es mejor ya que está entregando el contrato por la cadena de llamadas en lugar de construir en una casa de arena basada en la acción a distancia. Hay formas de pasar su contrato de forma más explícita, pero tiende a ser YAGNI intrincado (es decir, transmitir una llamada del método).

Debe lanzar las excepciones con anticipación y puede querer estar seguro en lugar de lamentar, por lo que los falsos positivos están bien. Una vez que descubras que es un falso positivo, lo arreglarás en la fuente.

Debe ser extremadamente raro que esté manejando excepciones en absoluto.La gran mayoría debería llegar a la cima, luego invocar un controlador de salida y registro. El resto se soluciona de manera adecuada devolviendo el resultado directamente y administrándolo. Cuando tiene un falso positivo de muchos usos, solo este que lo usa. No solo elimine el cheque en la raíz y rompa los muchos otros casos donde todavía hay un error.

Java es un lenguaje desafortunado porque no se puede decir que no pase nulo o que esta variable no sea nula.

Cuando falta esta característica, a menudo es mejor buscar nulos en sus fuentes, cosas como IO en vez de cada vez que se pasa algo a uno de sus métodos. De lo contrario, es una cantidad absurda de comprobación nula.

Puede aplicar este patrón para crear funciones para reemplazar su ifs para la verificación de parámetros si realmente lo necesita. Se podría sustituir id con el objeto en sí, tales como:

Object i_want(Object it) throws 
    if(it === null) 
     throw 
    return it; 

continuación:

void give_me(Object it) 
    this.it = Lib<Object>::i_want(it) 

Una pena no hay ningún tipo de tránsito.

O:

void i_get_it(Getter<Object> g) 
    this.it = Lib<Object>::i_want(g.gimme()) 

Es posible que tenga un captador específico en lugar de con el genérico.

O:

void i_need_it(Result<Object> r) 
    this.it = r.require() 

Si sólo lo hace en el límite vez (cuando se llama a las cosas fuera del lado de su control, donde no nulo resultado no puede garantizarse), aunque se prefiere hacer allí o para cualquier uso de un método documentado como devolver nulo y solo allí, ya que es donde realmente solo se necesita, eso significa que cuando obtienes un nulo al que no pertenece (los errores suceden) no vas a tener un tiempo fácil para descubrir de dónde vino. Se puede pasar mucho de IO y no producir una excepción de puntero nulo hasta que algo intente operar en él. Tales nulos se pueden pasar por el sistema durante días o incluso meses como una bomba de tiempo que está esperando estallar.

No lo haría yo mismo pero no culparía a la gente en algunos casos por implementar el enfoque de programación defensiva anterior que podría ser necesario debido al sistema de tipos rotos de Java que está suelto por defecto y no puede restringirse. En lenguajes robustos, null no está permitido a menos que explícitamente lo digas.

Tenga en cuenta que aunque lo llamo roto, he usado lenguajes significativamente más flexibles durante décadas para construir sistemas grandes, complejos y críticos sin tener que ensuciar la base de código con controles superfluos. La disciplina y la competencia son las que determinan la calidad.

Un falso positivo es cuando se produce un resultado o una condición que supone que es un resultado que no puede ser manejado por todas las personas que llaman, pero resulta que al menos una persona que llama puede manejarlo adecuadamente. En ese caso, no maneja la excepción sino que le da el resultado al que llama. Hay muy pocas excepciones a esto.

Java 8 tiene Opcional pero realmente no parece útil. Es un caso espantoso del efecto de plataforma interna que intenta implementar una nueva sintaxis como clases y termina teniendo que agregar la mitad de la sintaxis de Java existente, lo que la hace muy torpe. Como suele ser habitual en Java OOP, resuelve todos los problemas de las personas que quieren menos chocolate agregando más dulce de azúcar y complicando las cosas.Si realmente quieres encadenar de esa manera, deberías probar algo como kotlin, que implementa esa sintaxis directamente.

un lenguaje moderno, significa que usted no tiene que preocuparse por la mayor parte de esto y simplemente tener:

void i_need_it(Object it) 
    this.it = it 

void i_want_it(Object? it) 
    this.it = it 

Un lenguaje moderno incluso podría devolver el objeto original para un método sin un retorno (sustituir vacío con auto como el estándar y el auto-retorno) para que la gente pueda tener su encadenamiento elegante o cualquier otra cosa que esté de moda estos días en la programación.

No puede tener una fábrica con una clase base que le proporcione un Null o NotNull, ya que igual tendrá que pasar la clase base y eso será una violación de tipo cuando diga que quiere NotNull.

Es posible que desee jugar con los aspectos, análisis estático, etc. aunque eso es un poco un agujero de conejo. Toda la documentación debe indicar si puede devolverse el valor nulo (aunque indirectamente, se puede omitir).

Puedes hacer una clase como MightHave para envolver tu resultado en donde puedes poner en práctica métodos como get, require y has si no te gustan las estáticas pero quieres comer y si bien también está en el ámbito de la leve convolución y jugando con todas sus firmas de métodos en todas partes, boxeando todo por todos lados, una solución fea. Es realmente útil como una alternativa a esos raros casos de manejo de excepciones donde las excepciones parecen útiles debido a la complejidad del gráfico de llamadas y el número de incógnitas presentes en las capas.

Un caso excepcionalmente raro es cuando su fuente sabe qué excepción arrojar, pero no si debe arrojarse, pero es difícil de transmitir (aunque el acoplamiento de dos capas a una distancia donde algo puede suceder entre ellas debe abordarse con precaución). Algunas personas también pueden querer esto porque puede dar fácilmente una pista de dónde vino el elemento faltante y dónde se lo requirió, lo cual es algo que los controles pueden no dar (es probable que fallen cerca de la fuente, pero no se garantiza). Se debe tener precaución ya que este tipo de problemas podrían ser más indicativos de un control de flujo deficiente o una complejidad excesiva que un problema con polimorfismo justificado, codificación meta/dinámica y similares.

Se debe tener cuidado con cosas tales como valores por omisión o el patrón de objeto nulo. Ambos pueden terminar ocultando errores y convirtiéndose en las mejores suposiciones o una cura peor que la enfermedad. Cosas tales como un patrón NullObject y Opcional a menudo se pueden utilizar para simplemente desactivar o más bien bi-pasar el manejo de errores incorporado en su intérprete.

predeterminados no son siempre malas, pero pueden caer en el ámbito de adivinar. Ocultar los errores del usuario termina configurándolos para que fallen. Los valores predeterminados (y la desinfección) siempre deben pensarse cuidadosamente.

Cosas como el patrón NullObject y Opcional pueden ser utilizadas en exceso para desactivar eficazmente la comprobación del puntero nulo. Simplemente hace la suposición de que nulo nunca es un error que termina con programas haciendo algo, no otros, pero usted no sabe qué. En algunos casos, esto puede tener resultados divertidísimos, como si la tarjeta de crédito del usuario es nula. Asegúrate de utilizar estas cosas si no las estás usando para simplemente envolver todo tu código en una captura de prueba que ignore una excepción de puntero nulo. Esto es muy común porque las personas tienden a corregir errores donde se lanzó la excepción. En realidad, el error real tiende a estar más alejado. Usted termina con una falla en el manejo de IO que devuelve erróneamente nulo y ese nulo pasa por todo el programa. En lugar de solucionar esa única fuente de nulo, la gente intentará solucionarlo en todos los lugares donde alcanza un error.

patrón o patrones NullObject MagicNoop tienen su lugar, pero no son de uso común.No debe usarlos hasta que sea inmediatamente evidente que son útiles de una manera justificada que no va a causar más problemas de los que resuelve. Algunas veces, un noop es efectivamente un error.