Back-End

6 mar, 2018

Projeto Lombok: Escrevendo menos código em Java

Publicidade

Java é uma linguagem de programação muito poderosa, mas todo mundo sabe que ela possui um design levemente verboso e muita dessa verbosidade se faz presente no nosso dia a dia quando precisamos utilizar algum Framework.

Lombok é uma biblioteca Java focada em produtividade e redução de código boilerplate que, por meio de anotações adicionadas ao nosso código, ensinamos o compilador (maven ou gradle) durante o processo de compilação a criar código Java.

Esses e outros motivos tornaram o Lombok uma das minhas principais escolhas ao iniciar um novo projeto.

Nunca mais escreva outro método getter, setter, equals, builder etc.

Adicionando a dependência em seu projeto

Para utilizar o Lombok basta adicionar a dependência no seu projeto, com o scope definido como provided.

Com essa configuração garantimos que não vamos carregar a biblioteca em memoria quando a aplicação estiver em execução porque que o bytecode gerado não vai possuir as anotações da biblioteca, mas sim todo o código que elas podem nos prover.

Apache Maven:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
    <scope>provided</scope>
</dependency>

Gradle:

repositories {
	mavenCentral()
}
plugins {
	id 'io.franzbecker.gradle-lombok' version '1.11'
	id 'java'
}
lombok {
	version = 1.16.20 
	sha256 = ""
}

Como usar durante o desenvolvimento?

Precisamos instalar um plugin do Lombok para que nossas IDEs consigam enxergar quais serão os métodos criados pelas anotações do Lombok e nós dar acesso a eles.

Felizmente, temos plugins para as principais IDEs do mercado: EclipseIntelliJ IDEA e Netbeans.

Evitando criar métodos Construtores, Getters e Setters

Uma prática muito comum no mundo Java é a utilização do padrão Java Bean (basicamente criamos um construtor vazio e encapsulamos os atributos dos nossos objetos usando métodos getter e setter), por isso, muitos frameworks fazem uso desse padrão para interagirem com nossos objetos.

Isso é tão comum que praticamente todas as IDEs possuem um atalho para criar código que atende esse padrão. Isso já nos ajuda e muito, no entanto, esse código vai viver em nossas fontes e também deve ser mantido, quando por exemplo um novo atributo é adicionado ou renomeado.

Vamos imaginar que queremos usar a classe User como uma entidade JPA, logo teríamos o seguinte código:

@Entity
public class User {
 
    @Id @GeneratedValue
    private Long id; 
    private String firstName;
    private String lastName;
    private String email;
    private Date age;
    private String gender;
 
    public User() {}
 
    public User(String firstName, String lastName, String email, date age, String gender) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.age = age;
        this.gender = gender;
    }
    // getters and setters
    ...
}

Essa é uma entidade bastante simples, mas mesmo assim, quando adicionamos os métodos getters e setters, acabamos ganhando bastante código sem valor para o nosso negócio.

Para piorar um pouco a situação, notamos em tempo de desenvolvimento que o atributo age não deveria ser do tipo Date e sim Integer. Quanto trabalho teríamos refatorando esse código?

Bastante! Por isso, agora vamos utilizar o Lombok nessa classe:

@Entity
@Getter @Setter @NoArgsConstructor
public class User {
 
    @Id @GeneratedValue
    private Long id; 
    private String firstName;
    private String lastName;
    private String email;
    private Date age;
    private String gender;
 
    public User(String firstName, String lastName, String email, Date age, String gender) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.age = age;
        this.gender = gender;
    }
 }

Adicionando as anotações @Getter e @Setter no topo da classe, dissemos ao Lombok que crie os métodos para todos os atributos da classe. Para criar um construtor vazio usamos a anotação @NoArgsConstructor.

Observe! Esse é o código da classe inteira, dessa vez não estou omitindo nada. Esta é uma economia significativa de código.

Agora, podemos alterar o atributo age sem precisar se preocupar com o tamanho da refatoração dentro da classe User e caso a gente adicione um atributo novo, também não precisamos nos preocupar em criar os métodos, porque isso agora é trabalho do Lombok.

Todavia, sabemos que não é uma boa prática criar getter e setter para todos os nossos atributos, um bom exemplo é o id com a anotação @GeneratedValue.

Sabemos que é responsabilidade da JPA inicializar um valor para esse atributo, logo, não faz sentido que exista um método setter para ele, como podemos resolver esse problema?

Simplesmente removemos as anotações @Getter @Setter do topo da classe e adicionamos na linha de cada atributo separadamente, note que somente o atributo id está sem uma anotação @Setter.

@Entity
@NoArgsConstructor
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
 
    public User(String firstName, String lastName, String email, Date age, String gender) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.age = age;
        this.gender = gender;
    }
 }

Não existe fadiga que não possa ser evitada! Notaram que ainda existe um construtor com todos os atributos da classe escrito?

Vamos reduzir mais código simplesmente adicionando a anotação @AllArgsConstructor.

@Entity
@NoArgsConstructor @AllArgsConstructor
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
 }

E tem mais coisas: toString, equals e hashCode

Outra situação que temos código gerado automaticamente pelas IDEs e vira nossa responsabilidade de manter são os métodos: toString, equals e hashCode.

Mas para nossa alegria, o Lombok também nos ajuda a evitar a fadiga com esses métodos.

@ToString

@Entity
@NoArgsConstructor @AllArgsConstructor 
@ToString
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
}

Você pode usar essa anotação em qualquer classe e vai ser gerado um método toString(). Por padrão, ele vai retornar o nome da sua classe, juntamente com cada campo, em ordem e separados por vírgulas.

Caso você queria ignorar alguns campos, basta passa-los no parâmetro exclude ou você também pode especificar exatamente quais os campos deseja usar no parâmetro of.

@Entity
@NoArgsConstructor @AllArgsConstructor 
@ToString(exclude="id")
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
}

@EqualsAndHashCode

@Entity
@NoArgsConstructor @AllArgsConstructor 
@EqualsAndHashCode
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
}

Assim como o @ToString, qualquer classe pode ser anotada com @EqualsAndHashCode e vai gerar os métodos equals() e hashCode() por padrão ele usará todos os campos não estáticos e não transientes.

E você também pode ignorar alguns campos no parâmetro exclude ou você também pode especificar exatamente quais os campos deseja usar, no parâmetro of.

@Entity
@NoArgsConstructor @AllArgsConstructor 
@EqualsAndHashCode(exclude={"firstName", "lastName", "gender"})
public class User {
 
    @Id @GeneratedValue
    @Getter private Long id; 
    @Getter @Setter private String firstName;
    @Getter @Setter private String lastName;
    @Getter @Setter private String email;
    @Getter @Setter private Date age;
    @Getter @Setter private String gender;
}

Evitando ainda mais a fadiga

Essa é uma dica muito prática que eu vejo ser bastante viável naqueles casos que temos que usar Objeto de Transferência de Dados (DTO) porque se encaixa no contexto de uso de todos as anotações que vimos anteriormente.

Pensando nisso, os mantedores do Lombok criaram a anotação @Data.

@Data
public class User {
 
    @Id @GeneratedValue
    private Long id; 
    private String firstName;
    private String lastName;
    private String email;
    private Date age;
    private String gender;
}

Implementando o padrão de projeto Builder

Talvez você seja como eu e dê preferência para trabalhar com objetos imutáveis, logo, métodos setters não devem existir em nossas classes e construtores com muitos parâmetros são um anti-padrão.

Como trabalhar com objetos imutáveis, então?

Podemos utilizar o padrão de projeto builder, que é ideal nesse tipo de situação. Ele vai nos prover uma maneira de criar objetos sem precisarmos de construtores e sem métodos setters em nossas classes.

Para que o Lombok gere o código para a gente, basta adicionar a anotação @Builder no topo da classe.

@Builder
public class User {
 
    @Id @GeneratedValue
    private Long id; 
    private String firstName;
    private String lastName;
    private String email;
    private Date age;
    private String gender;
}

Logger

Outro ponto muito comum no desenvolvimento de sistemas com Java é fazer log da execução dos nossos serviços e, para isso, costumamos criar uma instância de um Logger.

O mais comum de se utilizar é o SLF4J:

public class UserService {
 
    private static Logger LOG = LoggerFactory.getLogger(UserService.class);
 
    // LOG.debug(), LOG.info(), ...
 
}

Isso é tão comum que os desenvolvedores do Lombok se preocuparam em simplificá-lo para nós:

@Slf4j
public class UserService {
   // log.debug(), log.info(), ...
}

A anotação @Slf4j pode ser substituída por outras bibliotecas de log como por exemplo: @Log ou @CommonsLog.

O interessante é que não importa qual biblioteca você escolha o objeto de acesso será sempre o mesmo log, detalhe que facilita e muito nossas vidas caso tenhamos o interesse de mudar de biblioteca.

Reduzindo boilerplate code com Lombok

E aí gostou do Lombok? Lá no canal do Sou Java fizemos uma live comentando tudo isso de uma forma muito mais detalha. Você pode assistir através deste link: https://youtu.be/zT5Ww_I4_Rk.

Conclusão

Existem outros recursos que não apresentei aqui, por isso recomendo novamente que você assista nossa live no Youtube e depois leia a documentação do Lombok para obter mais detalhes e personalizações de uso.

Porque a maioria das anotações que mostrei possuem uma série de opções de personalização que você pode achar úteis para que a biblioteca gere as coisas mais compatíveis com suas práticas de desenvolvimento.

Espero que você tenha encontrado a motivação para dar a Lombok a chance de entrar no seu conjunto de ferramentas de desenvolvimento Java.

Experimente e aumente a sua produtividade!