
Nós do Grubhub usamos Java em quase todo o back-end. Essa é uma linguagem comprovada que comprovou sua velocidade e confiabilidade nos últimos 20 anos. Mas ao longo dos anos, a idade do "velho homem" ainda começou a afetar.
Java é 
uma das linguagens JVM mais populares , mas não a única. Nos últimos anos, ele vem competindo com Scala, Clojure e Kotlin, que fornecem novas funcionalidades e recursos de linguagem otimizados. Em resumo, eles permitem que você faça mais com códigos mais concisos.
Essas inovações no ecossistema da JVM são muito interessantes. Devido à concorrência, o Java é forçado a mudar para permanecer competitivo. O novo cronograma de lançamento de seis meses e várias JEP (propostas de aprimoramento do JDK) no Java 8 (Valhalla, Inferência de tipo variável local, tear) são a prova de que o Java permanecerá uma linguagem competitiva por anos.
No entanto, o tamanho e a escala do Java significam que o desenvolvimento está progredindo mais lentamente do que gostaríamos, sem mencionar o forte desejo de manter a compatibilidade com versões anteriores a todo custo. Em qualquer desenvolvimento, a primeira prioridade deve ser funções, mas aqui as funções necessárias foram desenvolvidas por muito tempo, se é que há, na linguagem. Portanto, no Grubhub, usamos o Projeto Lombok para otimizar e aprimorar o Java à nossa disposição no momento. O projeto Lombok é um plug-in de compilador que adiciona novas “palavras-chave” ao Java e transforma anotações em código Java, reduzindo o esforço de desenvolvimento e fornecendo algumas funcionalidades adicionais.
Configurar Lombok
O Grubhub sempre se esforça para melhorar o ciclo de vida do software, mas cada nova ferramenta e processo tem um custo a considerar. Felizmente, para conectar o Lombok, basta adicionar algumas linhas ao arquivo gradle.
O Lombok converte anotações no código-fonte em instruções Java antes que o compilador as processe: a dependência do 
lombok não 
lombok em tempo de execução, portanto, o uso do plug-in não aumentará o tamanho do assembly. Para 
configurar o Lombok com Gradle (também funciona com o Maven), basta adicionar as 
seguintes linhas ao arquivo 
build.gradle :
 plugins { id 'io.franzbecker.gradle-lombok' version '1.14' id 'java' } repositories { jcenter()  
Com o Lombok, nosso código fonte 
não será um código Java válido. Portanto, você precisará instalar um plug-in para o IDE, caso contrário, o ambiente de desenvolvimento não entenderá com o que está lidando. O Lombok suporta todos os principais IDEs Java. Integração perfeita. Todas as funções como “show use” e “go to deployment” continuam funcionando como antes, movendo você para o campo / classe correspondente.
Lombok em ação
A melhor maneira de conhecer o Lombok é vê-lo em ação. Considere alguns exemplos típicos.
Reviva o objeto POJO
Com os "bons e antigos objetos Java" (POJOs), separamos os dados do processamento para facilitar a leitura do código e otimizar as transferências de rede. Um POJO simples possui vários campos particulares, bem como getters e setters correspondentes. Eles fazem o trabalho, mas requerem muito código padrão.
O Lombok ajuda a usar o POJO de maneira mais flexível e estruturada, sem código adicional. É 
@Data que simplificamos o POJO subjacente com a anotação 
@Data :
 @Data public class User { private UUID userId; private String email; } 
@Data é apenas uma anotação conveniente que aplica várias anotações do Lombok de uma só vez.
- @ToStringgera uma implementação para o- toString(), que consiste em uma representação clara do objeto: o nome da classe, todos os campos e seus valores.
 
- @EqualsAndHashCodegera implementações de- equalse- hashCode, que por padrão usam campos não estáticos e não estacionários, mas são personalizáveis.
 
- @Getter / @Settergera getters e setters para campos particulares.
 
- @RequiredArgsConstructorcria um construtor com os argumentos necessários, onde os campos finais e a anotação- @NonNullsão- @NonNull(mais sobre isso abaixo).
Somente esta anotação cobre de forma simples e elegante muitos casos de uso típicos. Mas o POJO nem sempre cobre a funcionalidade necessária. 
@Data é uma classe totalmente modificável, cujo abuso pode aumentar a complexidade e limitar a simultaneidade, o que afeta negativamente a capacidade de sobrevivência do aplicativo.
Existe outra solução. Vamos voltar à nossa classe 
User , torná-la imutável e adicionar algumas outras anotações úteis.
 @Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; } 
A anotação 
@Value semelhante a 
@Data exceto que todos os campos são privados e finais por padrão, e os setters não são criados. Graças a isso, os objetos 
@Value imediatamente se tornam imutáveis. Como todos os campos são finais, não há construtor de argumentos. Em vez disso, o Lombok usa o 
@AllArgsConstructor . O resultado é um objeto totalmente funcional e imutável.
Mas a imutabilidade não é muito útil se você precisar apenas criar um objeto usando o construtor all-args. Como Joshua Bloch explica em seu livro Effective Java Programming, você deve usar construtores se tiver um grande número de parâmetros de designer. Aqui a classe 
@Builder entra em 
@Builder , gerando automaticamente a classe interna do construtor:
 User user = User.builder() .userId(UUID.random()) .email(“grubhub@grubhub.com”) .favoriteFood(“burritos”) .favoriteFood(“dosas”) .build() 
A geração de um construtor facilita a criação de objetos com um grande número de argumentos e a inclusão de novos campos no futuro. O método estático retorna uma instância do construtor para configurar todas as propriedades do objeto. Depois disso, a chamada para 
build() retorna a instância.
A 
@NonNull pode ser usada para 
@NonNull que esses campos não são nulos ao criar uma instância do objeto; caso contrário, uma 
NullPointerException lançada. Observe que o campo do avatar é anotado com 
@NonNull mas não definido. O fato é que a anotação 
@Builder.Default aponta para 
default.png por padrão.
Observe também como o construtor usa 
favoriteFood , o único nome de propriedade em nosso objeto. Ao colocar anotações 
@Singular em uma propriedade de coleção, o Lombok cria métodos especiais do construtor para adicionar itens à coleção individualmente e não para adicionar a coleção inteira ao mesmo tempo. Isso é especialmente bom para testes, porque as maneiras de criar pequenas coleções em Java não podem ser chamadas de simples e rápidas.
Por fim, o parâmetro 
toBuilder = true adiciona o método da instância 
toBuilder() , que cria um objeto construtor preenchido com todos os valores dessa instância. É tão fácil criar uma nova instância, preenchida previamente com todos os valores do original, para que resta alterar apenas os campos necessários. Isso é especialmente útil para 
@Value classes 
@Value , porque os campos são imutáveis.
Algumas notas personalizam ainda mais as funções especiais do setter. 
@Wither cria métodos 
@Wither para cada propriedade. Na entrada, o valor; na saída, o clone da instância com o valor atualizado de um campo. 
@Accessors permite que você configure setters criados automaticamente. O parâmetro 
fluent=true desativa as convenções get e set para getters e setters. Em certas situações, isso pode ser um substituto útil para o 
@Builder .
Se a implementação do Lombok não for adequada para a sua tarefa (e você analisou os modificadores de anotação), sempre poderá tirar e escrever sua própria implementação. Por exemplo, se você possui a classe 
@Data , mas um getter precisa de lógica personalizada, basta implementar esse getter. O Lombok verá que a implementação já foi fornecida e não a substituirá pela implementação criada automaticamente.
Com apenas algumas anotações simples, o POJO básico recebeu tantos recursos avançados que simplificam seu uso sem carregar o trabalho de nossos engenheiros, sem perder tempo ou aumentar os custos de desenvolvimento.
Removendo o Código do Modelo
O Lombok é útil não apenas para POJO: pode ser aplicado em qualquer nível do aplicativo. Os seguintes usos do Lombok são especialmente úteis em classes de componentes, como controladores, serviços e DAOs (objetos de acesso a dados).
O registro é um requisito básico para todas as partes do programa. Qualquer classe que faça um trabalho significativo deve escrever um log. Assim, o criador de logs padrão se torna um modelo para cada classe. O Lombok simplifica esse modelo em uma única anotação que identifica e instancia automaticamente um criador de logs com o nome de classe correto. Existem várias anotações diferentes, dependendo da estrutura do diário.
 @Slf4j  
Após declarar o logger, adicione nossas dependências:
 @Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; } 
A 
@FieldDefaults adiciona modificadores finais e privados a todos os campos. 
@RequiredArgsConstructor cria um construtor que configura uma instância do 
UserDao . A 
@NonNull adiciona validação no construtor e 
UserDao NullPointerException se a instância 
UserDao for zero.
Mas espere, isso não é tudo!
Existem muitas outras situações em que Lombok faz o seu melhor. As seções anteriores mostraram exemplos específicos, mas o Lombok pode facilitar o desenvolvimento em muitas áreas. Aqui estão alguns pequenos exemplos de como usá-lo com mais eficiência.
Embora a palavra-chave 
var aparecido no Java 9, a variável ainda pode ser reatribuída. Lombok tem a palavra-chave 
val , que imprime o tipo final de uma variável local.
 
Algumas classes com funções puramente estáticas não devem ser inicializadas. Uma maneira de impedir a instanciação é declarar um construtor privado que lança uma exceção. Lombok codificou esse modelo na anotação 
@UtilityClass . Ele gera um construtor privado que lança uma exceção, finalmente gera a classe e torna todos os métodos estáticos.
 @UtilityClass  
O Java é frequentemente criticado por verbosidade devido a exceções verificadas. Uma anotação separada do Lombok os corrige: 
@SneakyThrows . Como esperado, a implementação é bastante complicada. Ele não captura exceções nem quebra exceções em uma 
RuntimeException . Em vez disso, depende do fato de a JVM não verificar a consistência das exceções verificadas no tempo de execução. Somente javac faz isso. Portanto, o Lombok usa a conversão de bytecode no momento da compilação para desativar essa verificação. O resultado é um código executável.
 public class SneakyThrows { @SneakyThrows public void sneakyThrow() { throw new Exception(); } } 
Comparação lado a lado
As comparações diretas mostram melhor quanto código o Lombok salva. O plug-in IDE possui uma função "de-lombok" que converte aproximadamente a maioria das anotações do Lombok em código Java nativo ( 
@NonNull anotações 
@NonNull não 
@NonNull convertidas). Portanto, qualquer IDE com o plug-in instalado poderá converter a maioria das anotações em código Java nativo e vice-versa. Voltar à nossa classe de 
User .
 @Value @Builder(toBuilder = true) public class User { @NonNull UUID userId; @NonNull String email; @Singular Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = “default.png”; } 
A classe Lombok é apenas 13 linhas simples, legíveis e compreensíveis. Mas depois de executar o de-lombok, a classe se transforma em mais de cem linhas de código padrão!
 public class User { @NonNull UUID userId; @NonNull String email; Set<String> favoriteFoods; @NonNull @Builder.Default String avatar = "default.png"; @java.beans.ConstructorProperties({"userId", "email", "favoriteFoods", "avatar"}) User(UUID userId, String email, Set<String> favoriteFoods, String avatar) { this.userId = userId; this.email = email; this.favoriteFoods = favoriteFoods; this.avatar = avatar; } public static UserBuilder builder() { return new UserBuilder(); } @NonNull public UUID getUserId() { return this.userId; } @NonNull public String getEmail() { return this.email; } public Set<String> getFavoriteFoods() { return this.favoriteFoods; } @NonNull public String getAvatar() { return this.avatar; } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof User)) return false; final User other = (User) o; final Object this$userId = this.getUserId(); final Object other$userId = other.getUserId(); if (this$userId == null ? other$userId != null : !this$userId.equals(other$userId)) return false; final Object this$email = this.getEmail(); final Object other$email = other.getEmail(); if (this$email == null ? other$email != null : !this$email.equals(other$email)) return false; final Object this$favoriteFoods = this.getFavoriteFoods(); final Object other$favoriteFoods = other.getFavoriteFoods(); if (this$favoriteFoods == null ? other$favoriteFoods != null : !this$favoriteFoods.equals(other$favoriteFoods)) return false; final Object this$avatar = this.getAvatar(); final Object other$avatar = other.getAvatar(); if (this$avatar == null ? other$avatar != null : !this$avatar.equals(other$avatar)) return false; return true; } public int hashCode() { final int PRIME = 59; int result = 1; final Object $userId = this.getUserId(); result = result * PRIME + ($userId == null ? 43 : $userId.hashCode()); final Object $email = this.getEmail(); result = result * PRIME + ($email == null ? 43 : $email.hashCode()); final Object $favoriteFoods = this.getFavoriteFoods(); result = result * PRIME + ($favoriteFoods == null ? 43 : $favoriteFoods.hashCode()); final Object $avatar = this.getAvatar(); result = result * PRIME + ($avatar == null ? 43 : $avatar.hashCode()); return result; } public String toString() { return "User(userId=" + this.getUserId() + ", email=" + this.getEmail() + ", favoriteFoods=" + this.getFavoriteFoods() + ", avatar=" + this.getAvatar() + ")"; } public UserBuilder toBuilder() { return new UserBuilder().userId(this.userId).email(this.email).favoriteFoods(this.favoriteFoods).avatar(this.avatar); } public static class UserBuilder { private UUID userId; private String email; private ArrayList<String> favoriteFoods; private String avatar; UserBuilder() { } public User.UserBuilder userId(UUID userId) { this.userId = userId; return this; } public User.UserBuilder email(String email) { this.email = email; return this; } public User.UserBuilder favoriteFood(String favoriteFood) { if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>(); this.favoriteFoods.add(favoriteFood); return this; } public User.UserBuilder favoriteFoods(Collection<? extends String> favoriteFoods) { if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>(); this.favoriteFoods.addAll(favoriteFoods); return this; } public User.UserBuilder clearFavoriteFoods() { if (this.favoriteFoods != null) this.favoriteFoods.clear(); return this; } public User.UserBuilder avatar(String avatar) { this.avatar = avatar; return this; } public User build() { Set<String> favoriteFoods; switch (this.favoriteFoods == null ? 0 : this.favoriteFoods.size()) { case 0: favoriteFoods = java.util.Collections.emptySet(); break; case 1: favoriteFoods = java.util.Collections.singleton(this.favoriteFoods.get(0)); break; default: favoriteFoods = new java.util.LinkedHashSet<String>(this.favoriteFoods.size() < 1073741824 ? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3 : Integer.MAX_VALUE); favoriteFoods.addAll(this.favoriteFoods); favoriteFoods = java.util.Collections.unmodifiableSet(favoriteFoods); } return new User(userId, email, favoriteFoods, avatar); } public String toString() { return "User.UserBuilder(userId=" + this.userId + ", email=" + this.email + ", favoriteFoods=" + this.favoriteFoods + ", avatar=" + this.avatar + ")"; } } } 
Faremos o mesmo para a classe 
UserService .
 @Slf4j @RequiredArgsConstructor @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) public class UserService { @NonNull UserDao userDao; } 
Aqui está um exemplo de contraparte no código Java padrão.
  public class UserService { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class); private final UserDao userDao; @java.beans.ConstructorProperties({"userDao"}) public UserService(UserDao userDao) { if (userDao == null) { throw new NullPointerException("userDao is marked @NonNull but is null") } this.userDao = userDao; } } 
Classificação de efeito
A Grubhub possui mais de cem serviços comerciais de entrega de alimentos. Nós pegamos um deles e lançamos a função "de-lombok" no plug-in Lombok IntelliJ. Como resultado, cerca de 180 arquivos foram alterados e a base de código cresceu cerca de 18.000 linhas de código após remover 800 casos de uso do Lombok. Em média, cada linha Lombok salva 23 linhas Java. Com esse efeito, é difícil imaginar Java sem o Lombok.
Sumário
Lombok é um ótimo ajudante que implementa novos recursos de idioma sem exigir muito esforço do desenvolvedor. Obviamente, é mais fácil instalar o plugin do que treinar todos os engenheiros em um novo idioma e portar o código existente. Lombok não é onipotente, mas pronto para uso é poderoso o suficiente para realmente ajudar no trabalho.
Outra vantagem do Lombok é que ele mantém bases de código consistentes. Como temos mais de uma centena de serviços diferentes e uma equipe distribuída em todo o mundo, a coerência das bases de código facilita o dimensionamento das equipes e reduz a carga de alternar contextos ao iniciar um novo projeto. O Lombok funciona para qualquer versão desde o Java 6, para que possamos contar com sua disponibilidade em todos os projetos.
Para o Grubhub, isso é mais do que apenas novos recursos. No final, todo esse código 
pode ser escrito manualmente. Mas Lombok simplifica as partes chatas da base de código sem afetar a lógica de negócios. Isso permite que você se concentre nas coisas que são realmente importantes para os negócios e as mais interessantes para nossos desenvolvedores. O código do modelo Monton é uma perda de tempo para programadores, revisores e mantenedores. Além disso, como esse código não é mais escrito manualmente, ele elimina classes inteiras de erros de digitação. Os benefícios da 
@NonNull automática combinados com o poder do 
@NonNull reduzem a probabilidade de erros e ajudam nosso desenvolvimento, que visa entregar comida à sua mesa!