¿La mejor manera de manejar nulos en Java? [cerrado]

21

Tengo un código que falla debido a NullPointerException. Se llama a un método en el objeto donde el objeto no existe.

Sin embargo, esto me llevó a pensar en la mejor manera de solucionar esto. ¿Siempre codifico a la defensiva los nulos para que pueda probar el código futuro para las excepciones de puntero nulo, o debo corregir la causa del nulo para que no ocurra en sentido descendente?

¿Cuáles son tus pensamientos?

Shaun F
fuente
55
¿Por qué no "arreglar la causa del nulo"? ¿Puedes dar un ejemplo de por qué esta no es la única opción sensata? Claramente, algo debe alejarte de lo obvio. ¿Qué es? ¿Por qué no arreglar la raíz es tu primera opción?
S.Lott
Arreglar bien la "causa raíz" del problema puede ayudar a corto plazo, pero si el código aún no está comprobando defensivamente los nulos y es reutilizado por otro proceso que puede introducir un nulo, tendría que solucionarlo nuevamente.
Shaun F
2
@Shaun F: Si el código está roto, está roto. No entiendo cómo es posible que el código produzca nulos inesperados y no se solucione. Claramente, están sucediendo algunas cosas de "administración tonta" o "política". O algo que permita que el código erróneo sea de alguna manera aceptable. ¿Puedes explicar cómo no se arregla el código erróneo? ¿Cuál es el trasfondo político que te lleva a hacer esta pregunta?
S.Lott
1
"vulnerable a la posterior creación de datos que tenía nulos" ¿Qué significa eso? Si "puedo arreglar la rutina de creación de datos", entonces no hay más vulnerabilidad. ¿No puedo entender qué puede significar esta "creación de datos posterior que tenía valores nulos"? Software con errores?
S.Lott
1
@ S.Lott, encapsulación y responsabilidad única. Todo su código no "sabe" lo que hace su otro código. Si una clase acepta Froobinators, hace la menor cantidad posible de suposiciones sobre quién creó el Froobinator y cómo. Proviene del código "externo", o simplemente proviene de una parte diferente de la base del código. No estoy diciendo que no debas arreglar el código defectuoso; pero busque "programación defensiva" y encontrará a qué se refiere el OP.
Paul Draper

Respuestas:

45

Si nulo es un parámetro de entrada razonable para su método, corríjalo. Si no, arregle la llamada. "Razonable" es un término flexible, por lo que propongo la siguiente prueba: ¿Cómo debe el método entregar una entrada nula? Si encuentra más de una respuesta posible, entonces nulo no es una entrada razonable.

usuario281377
fuente
1
Es realmente tan simple como eso.
biziclop
3
La guayaba tiene algunos métodos de ayuda muy agradables que hacen que la verificación nula sea tan simple como Precondition.checkNotNull(...). Ver stackoverflow.com/questions/3022319/…
Mi regla es inicializar todo a valores predeterminados razonables si es posible y preocuparme por las excepciones nulas después.
davidk01
Thorbjørn: ¿Entonces reemplaza una NullPointerException accidental con una NullPointerException intencional? En algunos casos, hacer la verificación temprana puede ser útil o incluso necesario; pero me temo que hay muchas verificaciones nulas sin sentido que agregan poco valor y solo hacen que el programa sea más grande.
usuario281377
66
Si nulo no es un parámetro de entrada razonable para su método, pero es probable que las llamadas del método pasen nulo accidentalmente (especialmente si el método es público), es posible que también desee que su método arroje un valor IllegalArgumentExceptioncuando sea nulo. Esto indica a las personas que llaman sobre el método que el error está en su código (en lugar de en el método en sí).
Brian
20

No use nulo, use Opcional

Como ha señalado, uno de los mayores problemas con nullJava es que puede usarse todas partes , o al menos para todos los tipos de referencia.

Es imposible decir que podría ser null y qué no podría ser.

Java 8 presenta un patrón mucho mejor: Optional .

Y ejemplo de Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Si cada uno de estos puede o no devolver un valor exitoso, puede cambiar las API a Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

Al codificar explícitamente la opcionalidad en el tipo, sus interfaces serán mucho mejores y su código será más limpio.

Si no está utilizando Java 8, puede mirar com.google.common.base.Optional en Google Guava.

Una buena explicación del equipo de Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Una explicación más general de las desventajas de nulo, con ejemplos de varios idiomas: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nonnull, @Nullable

Java 8 agrega estas anotaciones para ayudar a las herramientas de verificación de código como IDE a detectar problemas. Son bastante limitados en su efectividad.


Comprueba cuándo tiene sentido

No escriba el 50% de su código comprobando nulo, especialmente si no hay nada sensato que su código pueda hacer con un nullvalor.

Por otro lado, si nullpudiera usarse y significar algo, asegúrese de usarlo.


En última instancia, obviamente no puede eliminar nullde Java. Recomiendo sustituir la Optionalabstracción siempre que sea posible, y comprobar nullesas otras veces que puede hacer algo razonable al respecto.

Paul Draper
fuente
2
Normalmente, las respuestas a las preguntas de cuatro años terminan eliminadas debido a la baja calidad. Buen trabajo hablando de cómo Java 8 (que no existía en ese entonces) puede resolver el problema.
pero irrelevante para la pregunta original, por supuesto. ¿Y qué decir que el argumento es opcional?
Jwenting
55
@jwenting Es completamente relevante para la pregunta original. La respuesta a "¿Cuál es la mejor manera de clavar un clavo con un destornillador" es "Usar un martillo en lugar de un destornillador".
Daenyth
@jwenting, creo que lo cubrí, pero para mayor claridad: si el argumento no es opcional, normalmente no verificaría si es nulo. Tendrá 100 líneas de verificación nula y 40 líneas de sustancia. Verifique si hay nulo en las interfaces más públicas o cuando nulo realmente tiene sentido (es decir, el argumento es opcional)
Paul Draper
44
@ J-Boss, cómo, en Java, usted no tiene una marcada NullPointerException? A NullPointerExceptionpuede suceder literalmente cada vez que llama a un método de instancia en Java. Lo tendrías throws NullPointerExceptionen casi todos los métodos.
Paul Draper
8

Hay varias maneras de manejar esto, agregarle código a su código if (obj != null) {}no es ideal, es desordenado, agrega ruido al leer el código más adelante durante el ciclo de mantenimiento y es propenso a errores, ya que es fácil olvidarse de hacer esta envoltura de la placa de la caldera.

Depende de si desea que el código continúe ejecutándose silenciosamente o falle. Es el nullerror o una condición esperada.

Lo que es nulo

En cada definición y caso, Null representa la falta absoluta de datos. Los valores nulos en las bases de datos representan la falta de un valor para esa columna. un nulo Stringno es lo mismo que un vacío String, un nulo intno es lo mismo que CERO, en teoría. En la práctica "depende". Vacío Stringpuede ser una buena Null Objectimplementación para la clase String, ya Integerque depende de la lógica empresarial.

Las alternativas son:

  1. El Null Objectpatrón Cree una instancia de su objeto que represente el nullestado e inicialice todas las referencias a ese tipo con una referencia a la Nullimplementación. Esto es útil para objetos de tipo de valor simple que no tienen muchas referencias a otros objetos que también podrían ser nully se espera que sea nullun estado válido.

  2. Utilice herramientas orientadas a Null Checkeraspectos para tejer métodos con un aspecto que evite que los parámetros sean nulos. Esto es para casos donde nullhay un error.

  3. Uso assert()no mucho mejor que el if (obj != null){}pero menos ruido.

  4. Utilice una herramienta de cumplimiento de contratos como Contratos para Java . El mismo caso de uso que algo como AspectJ pero más nuevo y usa Anotaciones en lugar de archivos de configuración externos. Lo mejor de ambos trabajos de Aspectos y Afirmaciones.

1 es la solución ideal cuando se sabe que los datos entrantes son null necesitan y deben reemplazarse con algún valor predeterminado para que los consumidores no tengan que lidiar con todo el código repetitivo de verificación nula. La comprobación de los valores predeterminados conocidos también será más expresiva.

2, 3 y 4 son solo generadores de excepciones alternativos convenientes para reemplazar NullPointerException por algo más informativo, que siempre es y mejora.

En el final

nullen Java es casi en todos los casos un error lógico. Siempre debe esforzarse por eliminar la causa raíz de NullPointerExceptions. Debe esforzarse por no utilizar las nullcondiciones como lógica empresarial. if (x == null) { i = someDefault; }simplemente realice la asignación inicial a esa instancia de objeto predeterminada.


fuente
Si es posible, el patrón de objeto nulo es la forma de hacerlo, es la forma más elegante de manejar un caso nulo. ¡Es raro que quieras un nulo para hacer explotar cosas en producción!
Richard Miskin
@ Richard: a menos que nullsea ​​inesperado, entonces es un verdadero error, entonces todo debería detenerse por completo.
El valor nulo en las bases de datos y en el código de programación se maneja de manera diferente en un aspecto importante: en la programación null == null(incluso en PHP), pero en las bases de datos, null != nullporque en las bases de datos representa un valor desconocido en lugar de "nada". Dos incógnitas no son necesariamente iguales, mientras que dos nada son iguales.
Simon Forsberg
8

Agregar comprobaciones nulas puede hacer que las pruebas sean problemáticas. Mira esta gran conferencia ...

Echa un vistazo a la conferencia de Google Tech: "Las conversaciones de código limpio: ¡no busques cosas!" habla de eso alrededor del minuto 24

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

La programación paranoica implica agregar cheques nulos en todas partes. Parece una buena idea al principio, sin embargo, desde una perspectiva de prueba, hace que sea difícil manejar su prueba de tipo de cheque nulo.

Además, cuando crea una condición previa para la existencia de algún objeto como

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

le impide crear House ya que lanzará una excepción. Suponga que sus casos de prueba están creando objetos simulados para probar algo que no sea Puerta, bueno, no puede hacer esto porque se requiere puerta

Los que han sufrido el infierno de la creación simulada son muy conscientes de este tipo de molestias.

En resumen, su conjunto de pruebas debe ser lo suficientemente robusto como para detectar puertas, casas, techos o lo que sea sin tener que ser paranoico al respecto. En serio, ¿qué tan difícil es agregar una prueba de verificación nula para objetos específicos en su prueba :)

Siempre debe preferir las aplicaciones que funcionan porque tiene varias pruebas que PRUEBAN que funciona, en lugar de ESPERAR que funcione simplemente porque tiene un montón de verificaciones nulas precondicionales en todo el lugar

Constantin
fuente
4

tl; dr : es BUENO verificar nulls inesperados pero MALO para que una aplicación intente hacerlos buenos.

Detalles

Claramente, hay situaciones donde null es una entrada o salida válida para un método, y otras donde no lo es.

Regla 1:

El javadoc para un método que permite un nullparámetro o devuelve un nullvalor debe documentarlo claramente y explicar qué nullsignifica.

Regla # 2:

Un método API no debe especificarse como aceptación o devolución a nullmenos que haya una buena razón para hacerlo.

Dada una especificación clara del "contrato" de un método en relación con los visados null, es un error de programación pasar o devolver un lugar nulldonde no debe hacerlo.

Regla # 3:

Una aplicación no debe intentar "hacer buenos" errores de programación.

Si un método detecta un null que no debería estar allí, no debe intentar solucionar el problema convirtiéndolo en otra cosa. Eso solo oculta el problema del programador. En su lugar, debe permitir que ocurra el NPE y provocar un error para que el programador pueda descubrir cuál es la causa raíz y solucionarlo. Esperemos que la falla se note durante las pruebas. Si no, eso dice algo sobre su metodología de prueba.

Regla # 4:

Siempre que sea posible, escriba su código para detectar errores de programación temprano.

Si tiene errores en su código que resultan en muchos NPE, lo más difícil puede ser averiguar de dónde nullprovienen los valores. Una forma de facilitar el diagnóstico es escribir su código para que nullse detecte lo antes posible. A menudo puede hacer esto junto con otros controles; p.ej

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Obviamente, hay casos en los que las reglas 3 y 4 deben ser moderadas con la realidad. Por ejemplo (regla 3), algunos tipos de aplicaciones deben intentar continuar después de detectar probablemente errores de programación. Y (regla 4) puede haber demasiada comprobación de parámetros incorrectos. un impacto en el rendimiento)

Stephen C
fuente
3

Recomendaría arreglar el método para estar a la defensiva. Por ejemplo:

String go(String s){  
    return s.toString();  
}

Debería estar más en la línea de esto:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

Me doy cuenta de que esto es absolutamente trivial, pero si el invocador espera que un objeto le dé un objeto predeterminado que no hará que se pase un valor nulo.

Woot4Moo
fuente
10
Esto tiene un efecto secundario desagradable en que la persona que llama (programador que codifica contra esta API) podría desarrollar el hábito de pasar nulo como parámetro para obtener un comportamiento "predeterminado".
Goran Jovic
2
Si esto es Java, no deberías usarlo new String()en absoluto.
biziclop
1
@biziclop en realidad es mucho más importante de lo que la mayoría cree.
Woot4Moo
1
En mi opinión, tiene más sentido colocar una afirmación y lanzar una excepción si el argumento es nulo, ya que es claramente el error de la persona que llama. No "arregles" silenciosamente los errores de la persona que llama; en su lugar, hágales saber de ellos.
Andres F.
1
@ Woot4Moo Entonces escriba su propio código que arroje una excepción entonces. Lo importante es informar a la persona que llama que sus argumentos pasados ​​son incorrectos y decirles lo antes posible. La corrección silenciosa de nulls es la peor opción posible, peor que lanzar un NPE.
Andres F.
2

Las siguientes reglas generales sobre nulo me han ayudado mucho hasta ahora:

  1. Si los datos provienen de fuera de su control, verifique sistemáticamente los valores nulos y actúe de manera adecuada. Esto significa lanzar una excepción que tenga sentido para la función (marcada o desmarcada, solo asegúrese de que el nombre de la excepción le diga exactamente qué está sucediendo). Pero NUNCA no sea que pierda un valor en su sistema que pueda llevar sorpresas.

  2. Si Null está dentro del dominio de valores apropiados para su modelo de datos, trátelo adecuadamente.

  3. Cuando devuelva valores, intente no devolver valores nulos siempre que sea posible. Siempre prefiera Listas vacías, cadenas vacías, patrones de objetos nulos. Mantenga los valores nulos como valores devueltos cuando esta sea la mejor representación posible de datos para un caso de uso dado.

  4. Probablemente el más importante de todos ... Pruebas, pruebas y prueba de nuevo. Cuando pruebe su código, no lo pruebe como un codificador, pruébelo como una dominatriz psicópata nazi e intente imaginar todo tipo de formas de torturar ese código.

Esto tiende un poco al lado paranoico con respecto a los nulos que a menudo conducen a fachadas y proxies que interconectan los sistemas con el mundo exterior y los valores estrictamente controlados en el interior con abundante redundancia. El mundo exterior aquí significa casi todo lo que no codifiqué. Lleva un tiempo de ejecución de costo, pero hasta ahora rara vez tuve que optimizar esto creando "secciones nulas seguras" de código. Sin embargo, debo decir que en su mayoría creo sistemas de larga duración para el cuidado de la salud y lo último que quiero es que el subsistema de interfaz que lleva sus alergias al yodo al escáner CT se bloquee debido a un puntero nulo inesperado porque alguien más en otro sistema nunca se dio cuenta de que los nombres podrían contener apóstrofes o caracteres como 但 耒耨。

de todos modos ... mis 2 centavos

Newtopian
fuente
1

Yo propondría usar el patrón Option / Some / None de lenguajes funcionales. No soy un especialista en Java, pero uso intensivamente mi propia implementación de este patrón en mi proyecto C #, y estoy seguro de que podría convertirse al mundo Java.

La idea detrás de este patrón es: si es lógico tener una situación en la que existe la posibilidad de ausencia de un valor (por ejemplo, cuando se recupera de la base de datos por id), se proporciona un objeto de tipo Opción [T], donde T es un valor posible . I caso de ausencia de un objeto de valor de la clase None [T] devuelto, en caso de que exista la existencia del valor - objeto de Some [T] devuelto, que contiene el valor.

En este caso, debe manejar la posibilidad de ausencia de valor, y si revisa el código, podría encontrar fácilmente un lugar de manejo incorrecto. Para obtener una inspiración de la implementación del lenguaje C #, consulte mi repositorio de bitbucket https://bitbucket.org/mikegirkin/optionsomenone

Si devuelve un valor nulo y es lógicamente equivalente a un error (por ejemplo, no existe un archivo o no se pudo conectar), debe lanzar una excepción o utilizar otro patrón de manejo de errores. La idea detrás de esto, de nuevo, termina con una solución, cuando debe manejar la situación de ausencia de valor, y podría encontrar fácilmente los lugares de manejo incorrecto en el código.

Hedin
fuente
0

Bueno, si uno de los posibles resultados de su método es un valor nulo, entonces debe codificar defensivamente para eso, pero si se supone que un método devuelve un valor no nulo, pero no lo haría, lo arreglaría con seguridad.

Como de costumbre, depende del caso, como con la mayoría de las cosas en la vida :)

jonezy
fuente
0

Las bibliotecas lang de Apache Commons proporcionan una forma de manejar valores nulos

El método defaultIfNull en la clase ObjectUtils le permite devolver un valor predeterminado si el objeto pasado es nulo

Mahmoud Hossam
fuente
0
  1. No use el tipo Opcional, a menos que realmente sea opcional, a menudo la salida se maneja mejor como una excepción, a menos que realmente esperara nulo como una opción, y no porque escriba código con errores regularmente.

  2. El problema no es nulo como un tipo que tiene sus usos, como señala el artículo de Google. El problema es que los nulos deben verificarse y manejarse, a menudo se pueden salir con gracia.

  3. Hay una serie de casos nulos que representan condiciones inválidas en áreas fuera de la operación de rutina del programa (entrada inválida del usuario, problemas de la base de datos, fallas de la red, archivos faltantes, datos corruptos), que es para lo que son las excepciones verificadas, manejarlas, incluso si es solo para iniciar sesión.

  4. El manejo de excepciones permite diferentes prioridades y privilegios operativos en la JVM en lugar de un tipo, como Opcional, que tiene sentido por la naturaleza excepcional de su ocurrencia, incluido el soporte temprano para la carga diferida prioritaria del controlador en la memoria, ya que no Lo necesito dando vueltas todo el tiempo.

  5. No necesita escribir controladores nulos en todo el lugar, solo donde es probable que ocurran, en cualquier lugar donde pueda acceder a un servicio de datos poco confiable, y dado que la mayoría de estos caen en pocos patrones generales que pueden resumirse fácilmente, realmente solo necesita una llamada de manejador, excepto en casos excepcionales.

Entonces, supongo que mi respuesta sería envolverlo en una excepción marcada y manejarlo, o corregir el código poco confiable si está dentro de su capacidad.

J-Boss
fuente