Código “flecha” y cláusulas de guardia

Hace poco me encargaron cambiar un código bastante viejo para que pudiéramos usarlo en una capa de Batch donde se requiere dividir bastante lo que se quiere hacer i.e. hacerlo en lo que Spring Batch llama Tasklets.

Obviamente llego el momento en que tuve que seguir el código para entender que es lo que hacía (además de preguntar qué es lo que se supone que debe hacer, cosa diferente) y mi sorpresa fue que me topé con un código que se veía similar a esto...

If( id != -1 ){
// Otros cálculos
    if( campo != null) {
        if( todoBien ){
            // aquí otro reporte
       }
    } else{
        // aquí crea un reporte
    }
} else {
   throw new IllegalStateException (“Algo malo paso”);
}

Como pueden observar, todo este código es un ejemplo de lo que popularmente se conoce como arrow code o código flecha i.e. la forma del código se asemeja a una punta de flecha debido a la cantidad de if’s anidados.

Quiero puntualizar en que este tipo de código hace el programar algo bastante propenso a errores y no se diga sobre la legibilidad; en mi caso particular se usaron pocos métodos, así que el método del que hablo tenía aproximadamente 200 líneas de código y muchos más if’s de los que podía contar.

Una buena forma de refactorizar este código para hacerlo más legible es usando cláusulas de guardia (Guard Clauses). Esto consiste en hacer verificaciones a parámetros dentro del código que eviten que el código continúe si la verificación falla. Con esto no solo haces que tu diseño sea fail-fast sino que también lo haces mucho más facil de entender.

En el código anterior puedes ver algo similar a esto:

If(algunaCondicion) {
    // Hacer un cálculo
} else {
    throw new IllegalStateException (“Algo malo paso”);
}

Que puede manejarse asi:

If(!algunaCondicion){
    throw new IllegalStateException(“Algo malo paso”);
}
// Hacer un cálculo

Lo cual es más legible y evita que el código subsecuente se llene de código defensivo. Si han usado librerías como Guava han visto algo similar en la clase Preconditions e.g.

Preconditions.checkState( algunaCondicion, “Algo malo paso” );

Y con esto también haces que tus condiciones sean positivas e.g. en lugar de siempre usar una negación asi  If(!algunaCondicion) simplemente pones la condición en forma positiva (lo cual muchos pensamos que ayuda a leer el código mas rapido).

Ahora, supongamos que queremos que nuestro framework de inyección de dependencias (en este caso Spring) inyecte ciertos objetos y además queremos asegurarnos que esos objetos no sean nulos por algún error de dedo; para hacer que nuestra aplicación falle al momento de cargar nuestro contexto y no mucho después (¡me ha pasado!). En este caso usaríamos algo similar a esto:

@PostConstruct
public void init(){
    Preconditions.checkNotNull(this.someField, “someField cannot be null.”);
    Preconditions.checkState(!this.someList.isEmpty, “someList cannot be empty”);
}

Claro, yo siempre opto por hacer un static import y esto quedaria asi:

@PostConstruct
public void init(){
    checkNotNull(this.someField, “someField cannot be null.”);
    checkState(!this.someList.isEmpty, “someList cannot be empty”);
}

Aquí la ventaja de Guava es que es fácil saber qué tipo de excepción va a lanzar el código. En checkNotNull se puede discernir que lanzara un NullPointerException y en checkState se puede entender que lanzara un IllegalStateException.

Para finalizar quiero hacer notar que a muchos programadores les disgusta el tener tanto codigo defensivo en sus métodos. He visto el caso en que algunos prefieren mover todos esos “checks” en un método aparte; al inicio del método principal. Esto no se me hace malo pero procuro dejarlos en el método donde se realizaran los calculos para saber inmediatamente que son y que no son argumentos válidos. Eso lo dejo a discusión de ustedes.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

más allá

Si ya estás usando Spring para DI con anotaciones, pues de una vez anota con @Required el someField y Spring revisa que se le ponga un valor, te ahorras la validación de que no sea null.

Pero esas validaciones que pones en el init() sólo sirven de verdad si esos atributos del objeto son inmutables; porque si hay un setter entonces alguien puede pasarle a tu componente una lista vacía. Por eso como dices, suele ser mejor validar un valor justo antes de usarlo. Al principio de un método es buen lugar para validar que los parámetros y el estado del objeto sean válidos.

Imagen de ElderMael

Ahi si no podria...

Desgraciadamente donde ando no puedo usar anotaciones. La otra bronca es que

Ya van dos proyectos donde trabajo en los que de plano las banearon. Creo que fue una mala decision pero no hay de otra; es cierto lo que dices: Spring ya te provee mejores formas de validar escenarios como esos.

Aunque por otro lado luego veo cosas como usar @Qualifier("beanName") en lugar de @Resource("beanName") y me "awito" como luego dicen.

Imagen de ezamudio

anotaciones

el problema es que @Autowired y @Qualifier salieron antes de JSR250 (o algo así es). Posteriormente agregaron soporte para @Resource, @PostConstruct y todo eso pero pues ya tenían sus propias anotaciones y tienen que seguir soportándolas un rato más...