NullReferenceException/ArgumentNullException

Leyendo al tío Bob (Robert Martin) en su libro Clean Code, me doy con que en uno de los capítulos sugiere algo verdaderamente elemental: no pasarle null a los métodos!

Bien, está claro que salvo algún que otro caso, los métodos no esperan nulos como valores válidos. Por lo tanto si le pasamos un null seguramente recibiremos un NullReferenceException como respuesta. Mucha gente cree que para defenderse de esto debe validar las entradas más o menos así:

public void SaveCustomer(Customer customer)
{
if (customer == null)
throw new ArgumentNullException("customer");

De esta forma, si ahora le pasamos un nulo como argumento, lo que logramos es recibir un ArgumentNullException en lugar de un NullReferenceException. No parece una verdadera mejora, ¿o sí? Lo cierto es que ambas excepciones, en este caso, nos dicen lo mismo: le pasamos un nulo a un método y no deberíamos y la única manera de lograr que el código funcione correctamente es NO haciéndolo.

Básicamente, si no vamos a pasarle nulos a los métodos las validaciones resultan superfluas y ensucian el código innecesariamente. Solo basta ver lo espantoso que quedan esas dos líneas de ejemplo para comprobarlo, aunque como ya dijimos no se trata de un tema de estética.

Ahora bien, muchos desarrolladores, influenciados seguramente por el Frameworks Design Guidelines, ponen irreflexivamente chequeo de nulos en todos sus métodos, incluso algunos lo hacen hasta en los internos y privados. Eso es un error.

Las Framework Design Guidelines son lineamientos para la creación de APIs y es justamente en ese contexto en donde ArgumentNullException cobra sentido. ArgumentNullException existe con el propósito de “salvar la reputación” del creador de una API. Es para evitar que los clientes (programadores) obtengan un NullReferenceException proveniente de lo profundo de una API de la cual no tienen el código y en su lugar reciban un alerta que les indica que son ellos mismos los culpables de la excepción que obtienen ya que están pasando un valor nulo a un método.

Más allá de que la validación de nulos puede aportarnos una detección algo más cercana del problema, lo cierto es que en el balance resulta a todas luces innecesario usarla en código que solo usaremos nosotros y que está bajo nuestro poder, salvo algunas excepciones específicas como la de crear barreras de contención.

Sin categoría

8 thoughts on “NullReferenceException/ArgumentNullException

  1. Hola Lucas!

    Este es uno de los mejores ejemplos de como el uso de Code Contracts soluciona esa problemática. Con Code Contracts puedes:

    1) Eliminar la ArgumentNullException y transformarla en un error de contrato, que puede materializarse como una excepción, como un log (que obviamente irá seguido de la NullReferenceException, claro :P) o como lo que desees.

    2) Separar la implementación propia del método, de estas comprobaciones “previas” que de hecho lo que están haciendo es esto: verificar parte del contrato (las precondiciones en este caso)

    3) Integrarlo fácilmente en tests unitarios. Cierto que los contratos no se deben probar, pero con code contracts te das cuenta en seguida si una prueba unitaria (por la razón que sea) viola alguno de los contratos establecidos.

    Dicho esto es una auténtica pena la poca difusión que está teniendo Code Contracts… 🙁

    Un abrazo!

  2. Lucas, querido compi de blog, mira que me suelen gustar tus entradas y esta no es menos pero en esta ocasion no estoy de acuerdo o por lo menos tengo un cierto grado de divergencia, por supuesto con Rober Martin también 🙂 :-)…
    Hay una diferencia clara entre un ArgumentNullException y un NullReferenceException, aunque el efecto final para el llamante sea igualmente una excepcion. La diferencia principal es que dejas claro las precondiciones de este método ( el comentario anterior de Code Contracts no me parece apropiado porque esto es en si ya un contrato, se puede oscurecer con cualquier arma pero no deja de ser lo mismo, de hecho es una forma admitida de Contract.Require).
    De igual manera podríamos pensar que no tenemos que chequear una parámetro de entrada y comprobar que es un caracter correcto, por ejemplo, ya que seguramente podríamos esperar que nos devolviera un FormatException..
    Es decir, a mi me gusta dejar claras mis precondiciones porque son una forma sencilla de documentar las mimas, aunque el código sea mio, puede que dentro de un tiempo me tenga que trabajar y ver mis precondiciones me ayuda, aunque puedan ser evidente y reiterativas para el caso de comprobaciones con (null).
    saludos
    Unai

  3. @Unai
    Creo que, de hecho, pensamos lo mismo… igual no me he expresado bien con mi comentario.

    A lo que yo iba, es que Code Contracts te permite definir claramente y explicitamente que es una precondición y separarlo de lo que no.
    Dicho de otra forma: sin code contracts no puedes saber fácilmente los contratos de un método (salvo mirar el código y interpetar las excepciones que lanza). Con Code Contracts tienes todas las precondiciones establecidas (y separadas del “código normal”).
    Las puedes mirar tu o lo puede hacer una herramienta.

    Dices que te gusta documentar las precondiciones, no? Pues a eso iba… 🙂

    Un abrazo!

    pd: Crees que existiría ArgumentNullException si Code Contracts hubiese existido desde la v1 del framework???

  4. @Eduard, los chequeos de null pueden tomar miles de formas: el ‘if null then throw’, helpers estáticos llamados ‘Guard’, ‘Arg’ o ‘ArgChecker’, decoraciones tales como [Required], [NotNull], CodeContracts, etc. La forma que tomen no cambia el hecho de que son innecesarias en la inmensa mayoría de los casos.

    @Unai, esta entrada no es acerca de la validación de entradas sino de un caso muy particular: el chequeo de nulos. Y aquí hay una diferencia, null es una precondición implícita en la mayoría de los métodos! No hace falta ir a ver el código para saber que al siguiente método no se le deben pasar nulos:

    public void SaveCustomer(Customer customerToSave);

    imaginemos la siguiente llamada:

    repository.SaveCustomer(customerToSave: null);

    De todos modos, eta entrada no busca incentivar ni desalentar el chequeo de nulos, lo que busca es que se usen en donde se deben, en donde tienen sentido y sobre todo que el desarrollador sepa por qué lo hace o deja de hacer.
    Quines chequean nulos de manera compulsiva (aún en donde no es necesario hacerlo) muchas veces no son conscientes de lo que se expone aquí ni mucho menos del impacto en performance asociado al tener lanzamiento de excepciones por todos lados (los métodos que lanzan excepciones no pueden estar inline).
    “Pero Lucas! no te entiendo… chequeas nulos o no?” Los chequeo en APIs, bloques que se van a usar por muchos programadores, en lugares en donde algún que otro me a “culpado” de que mi código lanza NullReferenceException, etc. Es decir, hay muchas razones técnicas y no técnicas para checquear nulos y yo lo hago, lo que sí, siempre intento tener un buen motivo para hacerlo.

  5. Lo siento Lucas, pero sigo sin estar de acuerdo, además ahora te has ido por otros cerros muy diferentes, implícita o no es una precondición, y sigo sin ver ningun argumento que me diga que es mejor opción obviarla. Hablar de rendimiento como expones es un sinsentido, que mas da que la lances tu que cuando llegues a un NullReference.. si llegas a esa situacion la cantidad de excepciones será la misma Lucas. Además mezclar un antripatron como Overthowing con esto no me parece apropiado, si tengo un método de mi interfaz que es susceptible de lanzar muchas expceciones ( me da igual que sea por mi contrato que por un NullReference) ya proporcionaré un método de prueba ( como marca el fix de este antipatron).

    Saludos
    Unai

  6. Unai, el tema del rendimiento no es un sinsentido, no es lo mismo el NullReferenceException lanzado por el CLR que el ArgumentNullException que lanzamos en nuestro código (verifica eso). Pero el punto central si tiene sentido o no verificarlas. Yo entiendo que en algunas situaciones es conveniente (y ya enumeré cuales son esas situaciones a mi criterio) mientras que en otras es redundante.
    Recuerda que no hablo de blancos y negros, de chequar siempre vs no chequear nunca. Por eso mismo no vas a encontrar en esta entrada argumento alguno en favor de obviarlas absolutamente siempre, como tampoco de chequearlas siempre.
    Ahora, como opinión personal, salvo en aquellos lugares que he mencionado, no chequeo nulos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *