Pásate a la programación funcional y olvida los NullPointerException

En el post anterior hice una introducción a la programación funcional y expuse algunas de sus ventajas, que hacen que nuestro código sea más limpio, más fácil de testear y con menos errores. Hoy quiero profundizar más en este aspecto y que veamos cómo podemos deshacernos del código de protección contra los NullPointerException.

pf

Para empezar, vamos a ver un ejemplo al que estamos acostumbrados todos los que programamos en Java:

String var = miMetodo();
String result;

if (var == null) {
result = "El valor de var es nulo";
} else {
result = String.format("La longitud de var es %d", var.length());
}

System.out.println(result);

Tenemos que “defendernos” contra la posibilidad de que la variable sea nula. Este mismo código puede escribirse de una forma funcional de la siguiente manera en Java 8:

String var = miMetodo();
String result = Optional.ofNullable(var)
        .map(v -> String.format("La longitud de var es %d", v.length()))
        .orElse("El valor de var es nulo");

System.out.println(result);

Hemos quitado el código de defensa “feo” poniendo uno más elegante y más fácil de entender. La clase Optional nos permite hacer una operación map si el valor no es nulo y si lo es aplicar otra operación o devolver directamente un valor.

Vamos a ver ahora otro ejemplo donde la programación funcional nos da herramientas para facilitarnos mucho el desarrollo de un código limpio. Esta vez vamos a usar un Array de String, donde vamos a sumar las longitudes de sus cadenas. Un código típico sería algo así:

String[] array = miMetodo();
String result;
if (array == null) {
    result = "El array es nulo";
} else {
    int length = 0;
    for (String str : array) {
        if (str != null) {
            length += str.length();
        }
    }

    result = String.format("La longitud de las cadenas es de %d", length);
}

System.out.println(result);

Otra vez mucho código de defensa y un código difícil de seguir. Veamos lo mismo en Java 8 con un acercamiento funcional:

String[] array = miMetodo();
String result = Optional.ofNullable(array)
        .map(a -> {
            int length = Stream.of(a)
                    .filter(s -> s != null)
                    .mapToInt(String::length)
                    .sum();
            return String.format("La longitud de las cadenas es de %d", length);
        })
        .orElse("El array es nulo");

System.out.println(result);

Este código al principio puede parecer extraño ya que no estamos habituados a lo funcional, pero una vez acostumbrados no querremos hacerlo de otra manera.

pf2

Además, una de las ventajas que tiene usar Lambdas en nuestro código es que podemos reutilizarlas. El ejemplo anterior podríamos haberlo escrito de esta manera haciendo que nuestro código se pueda reutilizar en otras partes de la aplicación:

String[] array = miMetodo();

ToIntFunction<String> stringLength = s -> Optional.ofNullable(s).map(String::length).orElse(0);
String result = Optional.ofNullable(array)
        .map(a -> {
            int length = Stream.of(a)
                    .mapToInt(stringLength)
                    .sum();
            return String.format("La longitud de las cadenas es de %d", length);
        })
        .orElse("El array es nulo");

System.out.println(result);

Hemos creado la variable stringLength, que es una función que nos devuelve la longitud de una cadena o 0 si es nula. Este código podemos reutilizarlo en otras partes de nuestra aplicación si lo sacamos a una clase, o mejor aún, a una interfaz de forma estática así:

public interface ToIntFunctions {
    ToIntFunction<String> stringLength = s -> Optional.ofNullable(s).map(String::length).orElse(0);
}

String[] array = miMetodo();

String result = Optional.ofNullable(array)
        .map(a -> {
            int length = Stream.of(a)
                    .mapToInt(ToIntFunctions.stringLength)
                    .sum();
            return String.format("La longitud de las cadenas es de %d", length);
        })
        .orElse("El array es nulo");

System.out.println(result);

Ya podemos reutilizar esa función y lo más importante, podemos testearla unitariamente. Estas funciones auxiliares y comunes hacen que nuestro código sea más reutilizable y nos ayudan a minimizar los errores o a corregirlos más rápido en caso de haberlos.

Pensemos ahora en el siguiente código. Prometo que lo he visto en un proyecto en el que trabajé. Muchas veces la realidad supera a la ficción:

List<String[]> arrays = miMetodo();
String result;
if (arrays == null || arrays.isEmpty()) {
    result = "La lista es nula o esta vacia";
} else {
    int length = 0;
    for (String[] array : arrays) {
        if (array != null) {
            for (String str : array) {
                if (str != null) {
                    length += str.length();
                }
            }
        }
    }
    result = String.format("La longitud de las cadenas es de %d", length);
    System.out.println(result);
}

La forma más fácil de hacer algo es hacerlo escribiendo lo primero que se te viene a la cabeza. La forma más óptima es parándose a pensar qué queremos hacer y después escribirlo. Este código se podría haber escrito de la siguiente manera:

List<String[]> arrays = miMetodo();
String result = Optional.ofNullable(arrays)
        .map(a -> {
            int length = a.stream()
                    .flatMap(s -> Optional.ofNullable(s)
                            .map(Stream::of)
                            .orElse(Stream.empty()))
                    .mapToInt(ToIntFunctions.stringLength)
                    .sum();
            return String.format("La longitud de las cadenas es de %d", length);
        })
        .orElse("La lista es nula o esta vacia");
System.out.println(result);

Con todas estas nuevas formas de enfocar los problemas ya no hay excusa para no hacer código limpio. Además, te recomiendo que leas Clean Code: A Handbook of Agile Software Craftsmanship, un libro que ayudará a que tu código sea más limpio y sencillo.

Llevo programando desde los 8 años cuando mis padres se compraron un Amstrad CPC 6128. Desde entonces no he parado y me encanta aprender lenguajes nuevos y aplicar las 3 R (Reutilizar, Reducir, Refactorizar) a los proyectos donde estoy. Actualmente trabajo como Arquitecto de Soluciones en Paradigma.

Ver toda la actividad de Rubén García Becerro

Recibe más artículos como este

Recibirás un email por cada nuevo artículo.. Acepto los términos legales

Escribe un comentario