Introdução: Por que "código limpo" não é sobre estética
Quando você começa a escrever em Kotlin, parece que a própria linguagem já resolve metade dos problemas. Não há boilerplate como em Java. Existem `data class`, lambdas, corrotinas. Mas se você simplesmente jogar o código "de qualquer jeito", em um mês você mesmo não conseguirá entendê-lo. E se uma equipe trabalha no projeto — seus colegas vão odiá-lo (ou você os odiará).
Código limpo em Kotlin não é sobre tabs em vez de espaços. É sobre como reduzir a carga cognitiva, tornar a lógica transparente e evitar bugs que se escondem em construções "convenientes". Neste artigo, mostrarei técnicas específicas: desde trabalhar com nullable até usar scope functions e sealed classes. Sem enrolação, apenas prática.
1. Null Safety: como não proliferar "?" e "!!"
Kotlin se orgulha de seu sistema de tipos, mas muitos escrevem como se fosse Java com "açúcar sintático". O principal antipadrão são cadeias de `?.let` e `!!` em pânico.
Ruim (legibilidade sofre):
user?.address?.city?.let { city -> println(city)} ?: run { println("No city")Bom (elegante e limpo):
val city = user?.address?.cityprintln(city ?: "No city")Use `!!` apenas se você tiver 100% de certeza de que o valor não é null (por exemplo, em testes ou após uma verificação em `if`). Em produção, isso é uma granada. Se você precisa converter nullable para non-null e tratar ambos os casos — use when ou o operador elvis.
2. Scope Functions: `apply`, `run`, `with`, `let`, `also` — quando usar o quê?
Esta é uma ferramenta poderosa, mas muitas vezes é abusada. A regra de ouro: não aninhe scope functions umas dentro das outras. Se você tem `list.apply { ... }.let { ... }` — está escrevendo um macarrão.
Regras de escolha:
apply— para configuração de objeto (retorna o próprio objeto). Ideal para builders.let— para executar uma ação com um valor non-null (frequentemente em par com `?.`).run— quando você precisa calcular um valor e retornar o resultado.with— para agrupar chamadas de um mesmo objeto sem retorno.also— para efeitos colaterais (logging, validação).
Exemplo limpo:
data class User(var name: String, var age: Int)
fun createUser(): User = User("Unknown", 0).apply { name = "Alice" age = 30}Não escreva user?.let { it.name } — é redundante. Apenas user?.name.
3. Extension Functions: não prolifere classes utilitárias
Em Java, você escreveria StringUtils.capitalize(str). Em Kotlin — str.capitalize(). Extension functions são uma forma de "complementar" uma classe alheia sem herdá-la.
Exemplo de extension útil:
fun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}Mas não exagere. Se uma extension function é usada em apenas um lugar — talvez ela deva ser privada. E não crie extension em `Any` — isso quebra o sistema de tipos.
4. Sealed Classes e When: sua principal ferramenta em vez de if-else
Quando você tem um conjunto finito de estados (por exemplo, o resultado de uma requisição de API), use sealed class. Isso garante que você tratou todas as variantes.
Abordagem correta:
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error(val message: String, val code: Int) : NetworkResult<Nothing>() object Loading : NetworkResult<Nothing>()}
fun handle(result: NetworkResult<String>) { when (result) { is NetworkResult.Success -> println("Data: ${result.data}") is NetworkResult.Error -> println("Error: ${resu