Los que conocemos las bondades de Java tenemos una relación más que especial con "NullPointerException", esa excepción que se arroja cuando se requería un objeto no nulo y, para sorpresa de todos, resulta que venía algo nulo. Sacado del javadoc estos son los casos en los cuáles se provoca:
Thrown when an application attempts to use
nullin a case where an object is required. These include:
- Calling the instance method of a
nullobject.- Accessing or modifying the field of a
nullobject.- Taking the length of
nullas if it were an array.- Accessing or modifying the slots of
nullas if it were an array.- Throwing
nullas if it were aThrowablevalue.
El sentimiento que me produce cuando veo esa palabra en la pantalla es similar a cuando hacía prácticas en la universidad, en sistemas operativos, y me salía un error con segmentation fault o broken pipe. Me deja frío porque no me aporta nada, aparte del mosqueo que "lo que sea" no funcione bien. Creo que no hay excepción más poco útil que esta y, por desgracia, una de las más habituales.
La reacción normal es: "se me habrá olvidado inicializar algo" ... vale, se te ha pasado instanciar una lista o un mapa... puede empeorar, lo más normal es que necesites usar el modo depuración para comprobar los valores de las variables que entran en juego hasta que pilles a la "null" ... no, amigo, esto es el cazador cazado... Creo que es una de las peores impresiones que puede dar un software, que arroje una excepción "NullPointerException".
Por supuesto, hay gente que se ha molestado en pensar como solventar esto y cuáles son las buenas prácticas a seguir para asegurar que lo que "das" no sea nulo. Si llega un objeto nulo a un método, desarrollado por otra persona, y accede a su contenido (por ejemplo, llama un método), se invoca al diablo y será culpa tuya, no suya por no controlarlo. Por eso digo "das", es quién aporta la información quién debe asegurarse de lo que transmite. Nulo caca, vacío o valor por defecto guay! :)
¿Cuál es una forma válida de solucionar "la excepción de la muerte"? Bien, aquí paso a comentar brevemente algo sobre el patrón NullObject y la Lazy-Initialization. Ambos conceptos vienen a cubrir algo similar, el no retornar un objeto nulo, sin posibilidad que no se haya inicializado y no tenga "cuerpo" produciendo excepciones en cuanto se acceda a cualquier de sus partes. Además, la inicialización perezosa, tiene un toque de optimización ya que promueve inicializar un objeto solo cuando realmente sea necesario.
Veamos algo de código:
public Service getService() {
if (service == null)
service = new MyServiceImpl(...); // Good enough default for most cases?
return service;
}
Este fragmento lo he sacado del Clean Code, página 185, donde Uncle Bob nos habla de Lazy-initialization. Y no hay mucha más complejidad. La idea es simple, cuando vayamos a crear un método que, al invocarse, retorne información nos aseguramos que, al menos, no tendrás problemas con "null". Además no sobrecarga el sistema con la construcción del objeto a no ser que realmente se vaya a usar. Es muy posible que ese código os recuerde a cosas como el patrón singleton, factorias e inyección de dependencias. En caso contrario pincha y léete todos los enlaces que acabo de poner ;)
La aproximación de NullObject se centra sobre todo en no permitir retornar un valor nulo, no mira si ya existe o cosas así. También hay un libro del Tío Bob donde habla de él (libro que todavía no es de mi propiedad, todavía...) y el señor Fowler también lo comenta en Refactoring como se indica en este enlace. Un NullObject no es más que un objeto con cuerpo pero que no hace nada. Como antes, código ven a mi:
public interface animal {
public void makeSound();
}
public class dog implements animal {
public void makeSound() {
System.out.println("Guau!");
}
}
public class NullAnimal implements animal {
public void makeSound() { }
}
He adaptado un fragmento que había en la wikipedia para mostrarlo en Java. Teniendo la posibilidad de instanciar objetos de este tipo nos quitamos los problemas de los NullPointerException. Además, los que nos pegamos con el testing, ¿qué es sino un objeto "fake"? Es un objeto trucado. Esto es igual. De esta forma se puede inyectar para evitar la referencia nula.
No quiero alargarme más. Como siempre hay ya gente que ha hablado de esto y bien por la red, aquí os recopilo algunos enlaces de ambas aproximaciones:

Evidentemente los objetos nulos se deben de chequear que son nulos cuando se devuelven no en medio del código donde estamos haciendo algo lógico.
LoQueSea loquesea = getLoQueSea();
if loquesea != null... MAL :-D
Y para los nullpointers de aplicaciones que están en producción...
http://www.nooooooooooooooo.com/
jum esto de comentar en plan tocho suele hacer que el comentario se pierda en el limbo ... espero que no sea el caso
jum me temo que el blog debe tener algn filtro anti-comentarios-tochos y no me deja ponerlo :-P ... para el que le pueda interesar he puesto el comentario en http://www.google.com/profiles/atreyu.bbb
No he leido Clean Code, pero espero que expliquen que ese fragmento de código no es aceptable desde el punto de vista de la concurrencia :-)
Buen artículo.
Aparte del tema de la concurrencia tb veo un par de problemas en la soluciones presentadas:
1.- El patron lazy initialization es muy goloso y yo lo uso bastante pero tiene sus lados oscuros. Un lado oscuro esta en el "Good enough default for most cases?", hay veces que no es sufcientemente bueno o no lo es en todos los casos o lo es y deja de serlo y da lugar a bugs muchisimo mas insidiosos y dificiles de corregir que el plano NPE. El otro lado oscuro es que si usas reflexion (tipo BeanUtils.describe) se te inicializan todos los campos aunque no quieras. Supongo que habra alguno mas. Tal vez no sean tan oscuros y tengan solucion...
2.- EL NullObject es el hermano pobre del Maybe, y digo pobre porque usa la herencia en una forma que no es muy adecuada o al menos elegante en mi opinion (NullAnimal???). El "patron" Maybe (ver mi anterior comentario) es generico y aplicable a todas las clases sin tener que crear clases mockos :-P al usar tipos parametrizados (Maybe). No te salva de tener que hacer la comprobacion (en plan (obj instanceof Just) o (!obj instanceof Nothing)) pero su ventaja es justamente que te obliga a hacerla en tiempo de compilacion si quieres extraer el valor no nulo y no lo deja a la buena memoria del pobre desarrollador.
Siento spamear tanto pero el filtro anti-xss se me ha comido los ">" y "<" y se ha quedado un poco cojo lo del maybe. He colgado el comentario original e mi buzz por si acaso...
Ja, ja, pobre Javier. (Perdón, es que me ha hecho gracia el "spam")
Ahora en serio. Me gustaría añadir otra razón por la que me ha llamado la atención del ejemplo de UncleBob: puede hacer imposible probar unitariamente esa clase. Ya sé, depende, pero en general es un poco chungo hacer un new de una clase. Vale, la idea es que si usas el setter, el getter te devuelve el colaborador "inyectado" y caso contrario te da un colaborador por defecto. Vale. Pero, ¿qué pasa si lo hacemos así por costumbre? Pues básicamente que nuestro objeto se comportará de una manera diferente dependiendo de si lo hemos "preparado" o no. Y eso, en general, me da un poco de mal rollo. Es como si te permitiera relajarte y poner sólo atención en los tests en los que el objeto está adecuadamente configurado... pero resulta que en producción, un mal día, se te olvida dejarlo bien, bien... y ea, no tienes un NullPointer pero sí un "fantasma del pasado". :-)
En cuanto a los comentarios de Javier, y con el sano interés de crear polémica y aumentar las visitas al blog de Kini, creo que complicar la legibilidad del código sólo para garantizar que los programadores no usan indebidamente el código es un poco excesivo. Yo creo que es mucho más fácil usar una semántica muy clara y si algo no puede ser "null", hacer un test que lo aclare. ;-)
PS
Los comentarios de Javier en http://www.google.com/buzz/atreyu.bbb/Q6xDmhZ3XQC/NullPointerExce... (lo siento, en su momento decidí que Buzz era demasiado para alguien tan mayor como yo) ;-)
Buenas a todos,
muchas gracias a todos por pasaros y aportar el granito de arena a la discusión que, al final, es parte del gusanillo de escribir artículos.
En esto de la informática suele haber soluciones buenas y mejores pero siempre relativas. Hay casos contados en los cuáles hay una única solución, LA SOLUCIÓN! :) Pero esa riqueza creo que también es buena. Nos hace pensar, eso está bien, no quiero ser parte de una mecánica, no somos mecánicos, somos dinámicos, innovadores y creamos cosas (con nuestras manos - artesanos).
Estas dos soluciones que planteo aquí creo que son de los más directo que se puede hacer para paliar el problema del Null. Merece la pena mirar el Maybe que comenta @jneira (y me molan las cosas de los otros lenguajes... algún día voy a acabar migrando definitivamente!).
Gracias de nuevo por pasaros! :)
Esta bien tener en cuenta todas las opciones posibles a la hora de enfrentarse a los problemas. Si me permitis hago un resumen.. @kinisoftware ha presentado dos:
* uno es "paternalista" respecto al codigo que llama ya que le asegura que nunca le devolvera null sino un objeto inicializado (lazy initialization). Como hemos comentado tiene sus inconvenientes a las que añadiria que el cliente pierde una opcion, justamente la de null o Nothing, que podria aprovechar para ejecutar otro codigo (si me devuelves Nothing llamo a otra clase, suspendo la ejecucion, me creo yo el valo por defecto, etc)
* otra opcion es usar el sistema de tipado para obligar al cliente a tener en cuenta la opcion de que no se devuelva nada. Es el caso del NullObject o Maybe (mas elegante en mi opinion). Como bien comenta @jmbeas tiene el inconveniente de hacer escribir mas codigo "defensivo" en ambos lados (mezclado con la logica programa) y el otro pequeño "inconveniente" es que tienes que tener un sistema de tipado estatico (java ya lo tiene)
* @jmbeas introduce otra opcion: el test que nos alerta y nos informa de si en algun caso que no debiera estar el null aparece. Tambien es mas codigo, pero opcional y separado del codigo de la logica del programa. Sin embargo tb tiene sus inconvenientes (en mi opinion y sin conocer a fondo la tdd, me correis si me equivoco): el test prueba casos, poniendo un estado(s) en el programa y comprobando que en ese estado (o estados) se cumplan una serie de condiciones, pero no crea una barrera "logica" continua como el chequeo del tipado o en otra opcion que ya esta olvidada pero muy interesante ( http://en.wikipedia.org/wiki/Design_by_contract ). Si los casos del test no estan bien elegidos tal vez los tests no te salven justo en el hueco que dejas sin cubrir (que ya sabemos que es por el que Murphy se encargara de colarse)
El diseño por contrato, para mi, es una especie de BDUF. Te planteas, antes de empezar a construir el artefacto, cómo vas a poder usarlo. Para mi es mucho más natural empezar a escribir los tests e ir refactorizando el diseño (si quieres incluso el contrato) a medida que voy descubriendo nuevas necesidades. En realidad no es muy diferente.
Otra cosa es el estilo de dónde validamos: si ponemos asserts o barreras lógicas para protegernos de parámetros inadecuados, estamos haciendo el código menos legible. Yo prefiero apelar a la responsabilidad de los programadores. Pero, ojo, no es lo mismo un programador que un usuario. Si desde la capa de presentación el usuario puede dejar un campo vacío, sería nuestra responsabilidad enviar al servicio de turno un NullObject indicando que ese campo está vacío. Así nos ahorramos la validación en la "capa de negocio" y la hacemos donde primero somos conscientes de ella: la capa de presentación.
Y entonces llegamos a la lucha del mantenimiento de las validaciones entre capas, los frameworks que nos ayudan/fastidian, etc. Pero eso mejor se lo dejamos a Kini para que escriba otro post. :-)
Por cierto aunque he llamado a Maybe "patron" en realidad es un ejemplo de un concepto de programacion, la Monada ( http://en.wikipedia.org/wiki/Monad_%28category_theory%29 ), relacionado con la Teoria de Categorias ( http://es.wikipedia.org/wiki/Teor%C3%ADa_de_categor%C3%ADas ) una teoria matematica abstracta cercana a la filosofia en la que haskell y otros lenguajes basan su sistema de tipado estatico. Asi que encima si la usas puedes presumir de que estas usando una monada :-P
Por cierto, se me olvidó mencionar, que este tipo de prácticas si las tienes desde el principio están geniales, pero si no, si heredas un proyecto o entras en uno que esto no se está llevando a cabo (osea, lo habitual) siempre te quedará la revisión de código estática en busca de posibles null pointer exception con, por ejemplo: PMD.
Hola Kini, pues un patrón más para lidiar con el molesto null pointer es usar el patrón Option. En el lenguaje Scala los traen "de serie".
Option es una interface genérica que envuelve un valor que puede ser null.
Echa un vistazo en:
http://blog.lostlake.org/index.php?/archives/50-The-Scala-Option-...
Se puede hacer algo similar en JAVA
Señor Amodeo,
Creo que ha caido usted en el pecado de escribir sin leer lo anterior. :-)
"Por ahí arriba" hay un comentario de un tal jmbeas que hace referencia a unos comentarios de otro tal jneira en http://www.google.com/buzz/atreyu.bbb/Q6xDmhZ3XQC/NullPointerExce... donde justamente habla de esto. ;-P
Cuidado, porque es un tocho y la letra mu chica y usted no está para esto. Ea, a mejorarse.
Un abrazo,
JMB
Es verdad ! Gracias JMB !
Para una vez que hago lectura diagonal de los comentarios (forzado por razones médicas)...
:-)
Salud !