Nueve anotaciones de Groovy que te harán la vida más fácil al desarrollar

Nueve anotaciones de Groovy que te harán la vida más fácil al desarrollar
Sin comentarios Facebook Twitter Flipboard E-mail

Los desarrolladores somos vagos. Es un hecho. Si podemos solucionar un problema escribiendo menos código y la solución es eficiente, mantenible, legible,... vamos a optar por ella sin pensarlo. Escribir menos código significa menos bugs, menos código que manterner y menos código que testear.

Después de haber aprendido en el artículo anterior la teoría sobre la metaprogramación en compile-time con Groovy en esta ocasión vamos a ver qué transformaciones nos ofrece Groovy _out-of-the-box_ para hacernos la vida más fácil a la hora de desarrollar.

Categorías de transformaciones

Todas las transformaciones que ofrece Groovy por defecto son transformaciones AST locales y se pueden dividir en distintas categorías en función del aspecto que cubren.

Generación de código

Se incluyen transformaciones que nos ayudan a eliminar código redundante e innecesario. Es código que típicamente escribimos pero que no aporta información útil. Posiblemente la mayoría de las anotaciones más utilizadas pertenecen a esta categoría.

@ToString

¿Recordais el libro Effective Java de Joshua Bloch? Es una de las "biblias" que todo desarrollador Java debería leer al menos una vez en la vida. En él hay un capítulo entero dedicado al método toString() en el que se explica con detalle por qué deberíamos implementar dicho método en nuestras clases. Veámoslo con un ejemplo:

class User {
    String name
    Integer age
}

def u = new User(name: 'Iván', age: 36)
println u // User@1d2a54b2
@groovy.transform.ToString
class User {
    String name
    Integer age
}

def u = new User(name: 'Iván', age: 36)
println u // User(Iván, 36)'

¿Mucho más legible verdad? Además la anotación se puede configurar con múltiples parámetros para incluir o no el nombre de las propiedades, el paquete, excluir las propiedades con valor nulo,...

@EqualsAndHashCode

¿Cuántas veces habeis implementado los métodos equals y hashCode en una clase? Y de esas, ¿cuántas veces lo habeis hecho mal? Probablemente más de las que os imaginais. De nuevo, si leeis _Effective Java_, la explicación de cómo deben ser las implementaciones de equals y hashCode ocupa 20 páginas en el libro. ¿Quereis saber cómo _escribir_ bien la implementación de equals y hashCode? No escribiéndola nosotros, dejando que el compilador se encargue de ella:

@groovy.transform.EqualsAndHashCode
class User {
    String name
    Integer age
}

def u1 = new User(name: 'Iván', age: 36)
def u2 = new User(name: 'Iván', age: 36)

assert u1 == u2
assert u1.hashCode() == u2.hashCode()

@TuppleConstructor

Esta anotación sirve para generar diferentes constructores en función de las propiedades de la clase:

@groovy.transform.TupleConstructor
class User {
    String name
    Integer age
}

// Constructor de mapa por defecto
def u1 = new User(name: 'Iván', age: 36)

// Constructores generador por @TupleConstructor
def u2 = new User('Iván', 36)
def u3 = new User('Iván')

Seguro que ahora mismo os estais acordando de esa clase que tenía 10 propiedades y una vez tuvisteis que crear un montón de constructores para distintos casos de uso...

@Canonical

Es bastante común querer implementar los métodos toString, equals y hashCode y además querer tener constructores en función de las propiedades. Esto implicaría anotar una clase con las tres anotaciones que hemos visto anteriormente. Es por ello que existe la anotación @Canonical que es la suma de las tres:

@groovy.transform.Canonical
class User {
    String name
    Integer age
}

def u1 = new User(name: 'Iván', age: 36)
def u2 = new User('Iván', 36) // @TupleConstructor
assert u2.toString() == 'User(Iván, 36)' // @ToString

assert u1 == u2 // @EqualsAndHashCode
assert u1.hashCode() == u2.hashCode() // @EqualsAndHashCode

@Builder

La última anotación que vamos a comentar de esta categoría nos permite crear builders para construir objetos con un API _fluida_.

@groovy.transform.builder.Builder
class User {
    String name
    Integer age
    Integer born
}

def u = User.builder()
    .name('Iván')
    .age(36)
    .born(1979)
    .build()

assert u.name == 'Iván'
assert u.age == 36
assert u.born == 1979

Podemos configurar el nombre del builder, los prefijos, incluir y excluir propiedades e incluso podemos definir nuestra propia estrategia de build.

Diseño de clases

Simplifican la implementación de patrones de diseño conocidos: _delegate_, _singleton_, _immutable_...

@Immutable

Ahora que está muy de moda el concepto de inmutabilidad y lenguajes funcionales tenemos una forma muy sencilla de escribir una clase inmutable. Si alguna vez lo habeis intentado sabreis que existen unas reglas (no tan triviales) que debemos cumplir para asegurar la inmutabilidad. Con esa anotación no tenemos que preocuparnos de nada, la aplicamos y listo.

@groovy.transform.Immutable
class User {
    String name
    Integer age
}

def u = new User(name: 'Iván', age: 36)

// Esto no compila porque no podemos
// extender de una clase final
class Admin extends User {
}

// Si intentamos modificar el nombre se lanzará una 
// excepción porque la propiedad es de sólo-lectura
try {
    u.name = 'John'
} catch (ReadOnlyPropertyException e) {
    println e
}

Mejoras en logging

Integran de una manera sencilla la mayoría de los frameworks de logging más utilizados.

@Log, @Log4j, @Log4j2, @Slfj4

Aunque realmente son cuatro anotaciones las he agrupado todas juntas (y las cuento como una) porque el funcionamiento es el mismo. La única diferencia es la biblioteca de logging que utilizan.

@groovy.util.logging.Log
class Saludador {
    void saludo() {
        log.info 'Saludo ejecutado'
        println 'Hola GenbetaDev!'
    }
}

Si anotamos una clase con una de las anotaciones de logging, el compilador generará un código equivalente al siguiente. Como vemos las diferencias son importantes puesto que no tenemos que preocuparnos por instanciar el logger ni tampoco por comprobar si el log level que estamos usando está activado para no perder el tiempo en intentar logar mensajes que serán descartados.

import java.util.logging.Level
import java.util.logging.Logger

class Saludador {
    private static final Logger log = Logger.getLogger(Saludador.name)
    void saludo() {
        if (log.isLoggable(Level.INFO)) {
            log.info 'Saludo ejecutado'
        }
        println 'Hola GenbetaDev!'
    }
}

Concurrencia declarativa

Simplifican patrones comunes de concurrencia de una manera declarativa.

Clonado y externalización

Anotaciones para facilitar la implementación de los interfaces Clonable y Externalizable.

Scripting seguro

Generan código que permitirá, por ejemplo, interrumpir la ejecución de un script.

Directivas del compilador

Este grupo incluye anotaciones que afectarán directamente en la semántica del código generado.

@CompileStatic

Es probablemente una de las anotaciones más utilizadas actualmente. Con ella estamos indicando al compilador que queremos que verifique y genere nuestro código _como si fuera Java_. Es decir, una clase o método anotados con @CompileStatic serán verificados en tiempo de compilación y perderemos las posibilidades de metaprogramación en runtime de Groovy. Con esta anotación el byte code generado es muy similar al que generaría el programa Java equivalente con lo que conseguimos que el rendimiento mejore.
En general, en la actualidad se considera una buena práctica anotar nuestro código no-dinámico con @CompileStatic.

Patrones de Swing

Implementan ciertos patrones muy útiles si estamos desarrollando una aplicación Swing.

Ayuda en los tests

Se incluyen un par de anotaciones utiles durante los test, incluyendo una para ayudar a depurar otras transformaciones AST y el propio compilador de Groovy.

Manejo de dependencias

Manejo de las dependencias utilizando Grape.

@Grab

Creo que esta es una de esas anotaciones que no es muy conocida pero que una vez la descubres piensas todos los problemas que te habría solucionado. Imaginad que estais escribiendo un script o pequeño código pero necesitais una dependencia externa. Usando @Grab declaramos la dependencia, importamos las clases que necesitemos y la utilizamos. La anotación se encarga de descargar la dependencia y añadirla al classpath en el momento de ejecutar el script.

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

// Equivalente
@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

Conclusiones

Hemos visto sólo unas pocas anotaciones de las más de 45 que incluye Groovy y que están pensadas para ayudarnos en nuestro día a día como desarrolladores. Algunas implementan ciertos métodos por nosotros, otras nos ayudan a escribir builders o generar clases inmutables y otras hacen que nuestro código se compile estáticamente y se verifique en compilación en lugar de ejecución. Si somos desarrolladores Groovy debemos conocer todo lo que nos ofrece el lenguaje para ayudarnos a ser más productivos y eficientes a la hora de desarrollar. Además, como comentaba al principio del artículo, somo vagos, así que ¿por qué querríamos escribir todo ese código pudiendo delegarlo en el compilador?

Si quieres conocer todas las transformaciones disponibles o profundizar más en el uso de las que he explicado, la documentación oficial es la mejor forma de hacerlo.

Comentarios cerrados
Inicio