Introdução: por que refatorar código legado em Kotlin não é apenas reescrever
Trabalhar com código legado em Kotlin não é apenas uma substituição mecânica de construções Java pela sintaxe da linguagem. É um processo estratégico, voltado para aumentar a legibilidade, segurança e desempenho do código sem alterar seu comportamento externo. Neste artigo, veremos técnicas avançadas de refatoração que ajudarão você a transformar um "zoológico" caótico de classes em um sistema organizado e sustentável. Não abordaremos conceitos básicos como var vs val — focaremos no que realmente muda a arquitetura.
1. Migração de Java: substituindo getters, setters e classes utilitárias
O problema mais comum em código legado é a abundância de classes Java com getters e setters explícitos, além de classes utilitárias estáticas. Kotlin oferece soluções elegantes.
1.1. Acesso baseado em propriedades em vez de JavaBeans
Em vez de escrever user.getName() e user.setName("Alice"), use propriedades do Kotlin. Isso não é apenas açúcar sintático: você obtém uma interface de acesso unificada que pode ser sobrescrita.
// Java legadopublic class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}
// Kotlin refatoradodata class User(val name: String) // Ou com var, se precisar de mutabilidadeSe a lógica do getter/setter for mais complexa (por exemplo, validação), use assessores personalizados:
class User { var name: String = "" set(value) { field = value.trim() // Corte automático de espaços }}1.2. Funções extension em vez de utilitários estáticos
Código legado frequentemente contém classes como StringUtils ou FileUtils. Em Kotlin, elas são substituídas por funções extension. Isso torna o código mais legível e se aproxima do estilo "método do objeto".
// Java: StringUtils.isNullOrEmpty(str)// Kotlin: str.isNullOrEmpty() (já existe na stdlib)
// Exemplo de sua própria extensionfun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}
// Usoval email = "user@example.com"if (email.isValidEmail()) { ... }2. Trabalhando com null safety: substituindo Optional e verificações de null
Código legado em Java frequentemente sofre de NullPointerException. Kotlin resolve esse problema no nível de tipos, mas durante a refatoração é preciso substituir cuidadosamente Optional e cadeias de verificações de null.
2.1. Substituindo Optional por tipos nullable
Em vez de Optional<String>, use String?. Isso reduz a sobrecarga de criação de objetos e torna o código mais conciso.
// Javapublic Optional<String> findName() { ... }
// Kotlinfun findName(): String? { ... }Para cadeias de chamadas, use o operador ?. e o operador elvis ?::
// Em vez de if (user != null && user.address != null) ...val city = user?.address?.city ?: "Unknown"2.2. Usando require() e check() para invariantes
Em vez de verificações manuais no início das funções, use funções embutidas:
fun processUser(user: User?) { requireNotNull(user) { "User must not be null" } // ou require(user != null) { ... } // Trabalho posterior com user (smart cast)}3. Refatoração de lógica condicional: classes sealed e when
Blocos enormes de if-else ou switch são um sinal de código legado. Em Kotlin, eles podem ser substituídos por sealed class e when exaustivo.
3.1. Substituindo enum com campos por sealed class
Se você tem um enum que armazena diferentes tipos de dados para cada valor