Neste tutorial, você vai aprender as boas práticas mais importantes ao escrever código Kotlin. Vamos cobrir convenções de nomenclatura, formatação, padrões idiomáticos e ferramentas de análise estática como ktlint e detekt. Seguir essas práticas torna o código mais legivel, mais fácil de manter e alinhado com o que a comunidade Kotlin espera. Se você já conhece o básico da linguagem — variaveis e tipos, funções e classes — esta pronto para dar esse proximo passo.

Convenções de Nomenclatura

A primeira coisa que qualquer pessoa percebe ao ler código alheio são os nomes. Kotlin segue convenções muito parecidas com as do Java, mas com algumas particularidades.

Pacotes devem usar letras minusculas e sem underscores:

// Bom
package com.meuapp.dominio.usuario

// Ruim
package com.meuApp.Dominio.Usuario

Classes e interfaces usam PascalCase. Nomes de data classes seguem a mesma regra:

// Bom
class ContaBancaria
data class Endereco(val rua: String, val numero: Int)
interface Repositorio

// Ruim
class conta_bancaria
data class endereco(val rua: String, val numero: Int)

Funções, propriedades e variaveis locais usam camelCase:

// Bom
fun calcularDesconto(valor: Double): Double { ... }
val nomeCompleto = "Maria Silva"

// Ruim
fun CalcularDesconto(valor: Double): Double { ... }
val nome_completo = "Maria Silva"

Constantes (propriedades const val ou valores em companion objects que são verdadeiramente constantes) usam SCREAMING_SNAKE_CASE:

companion object {
    const val TAXA_MAXIMA = 0.15
    const val TEMPO_LIMITE_MS = 5000L
}

Uma dica que vale ouro: evite abreviacoes obscuras. Prefira quantidadeDeItens a qtdItens. O código e lido muito mais vezes do que e escrito, entao investir em nomes claros economiza tempo de todo mundo.

Formatação e Ferramentas de Análise

Manter um estilo consistente em todo o projeto e fundamental, especialmente quando há mais de uma pessoa contribuindo. Duas ferramentas se destacam no ecossistema Kotlin.

ktlint

O ktlint e um linter e formatador que aplica as convenções oficiais de estilo do Kotlin. Ele pode ser adicionado ao projeto via Gradle:

// build.gradle.kts
plugins {
    id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
}

Com isso, você pode rodar ./gradlew ktlintCheck para verificar o estilo e ./gradlew ktlintFormat para corrigir automaticamente. O ideal e integrar isso ao pipeline de CI para que nenhum código fora do padrão entre no repositório.

detekt

Enquanto o ktlint cuida da formatação, o detekt vai além e faz análise estática de qualidade. Ele detecta code smells como funções muito longas, complexidade ciclomatica alta, magic numbers e muito mais.

// build.gradle.kts
plugins {
    id("io.gitlab.arturbosch.detekt") version "1.23.4"
}

detekt {
    config.setFrom("$projectDir/config/detekt/detekt.yml")
    buildUponDefaultConfig = true
}

Usar as duas ferramentas juntas garante que o código esta tanto bem formatado quanto saudavel em termos de qualidade.

Prefira when no Lugar de Cadeias de if-else

Uma das marcas registradas do Kotlin idiomatico e o uso de expressoes when no lugar de longas cadeias de if-else. Alem de ser mais legivel, o when funciona como expressao, ou seja, retorna um valor.

// Ruim: cadeia de if-else
fun classificarNota(nota: Int): String {
    if (nota >= 9) {
        return "Excelente"
    } else if (nota >= 7) {
        return "Bom"
    } else if (nota >= 5) {
        return "Regular"
    } else {
        return "Insuficiente"
    }
}

// Bom: when como expressao
fun classificarNota(nota: Int): String = when {
    nota >= 9 -> "Excelente"
    nota >= 7 -> "Bom"
    nota >= 5 -> "Regular"
    else -> "Insuficiente"
}

Quando você combina when com sealed classes, o compilador garante que todos os casos foram tratados, eliminando a necessidade do bloco else e protegendo contra esquecimentos.

Use Data Classes para Dados

Se uma classe existe primariamente para armazenar dados, ela deve ser uma data class. O compilador gera automaticamente equals(), hashCode(), toString(), copy() e funções de destructuring, eliminando código boilerplate.

// Ruim: classe regular para dados
class Usuario(val nome: String, val email: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Usuario) return false
        return nome == other.nome && email == other.email
    }

    override fun hashCode(): Int = 31 * nome.hashCode() + email.hashCode()
    override fun toString(): String = "Usuario(nome=$nome, email=$email)"
}

// Bom: data class
data class Usuario(val nome: String, val email: String)

A versão com data class faz exatamente a mesma coisa que a versão manual, só que em uma única linha. Menos código significa menos bugs.

Scope Functions: use com Criterio

Kotlin oferece cinco scope functions — let, run, with, apply e also. Cada uma tem seu caso de uso ideal, e a chave e não exagerar. Se você quer se aprofundar, veja o tutorial de lambdas.

// Bom: let para null check
val comprimento = nome?.let {
    println("Processando: $it")
    it.length
}

// Bom: apply para configurar objetos
val textView = TextView(context).apply {
    text = "Ola, mundo"
    textSize = 16f
    setTextColor(Color.BLACK)
}

// Ruim: aninhar scope functions desnecessariamente
val resultado = valor?.let { v ->
    v.toString().let { s ->
        s.uppercase().also { u ->
            println(u)
        }
    }
}

A regra de bolso e: se o aninhamento dificulta a leitura, quebre em variaveis intermediarias. Legibilidade sempre vem primeiro.

Evite Tipos Nullable Desnecessarios

O sistema de null safety do Kotlin e uma das suas maiores vantagens. Porem, muita gente vinda do Java acaba usando ? em tudo por habito. Pergunte-se sempre: essa propriedade realmente pode ser nula?

// Ruim: nullable sem necessidade
class Pedido(
    val id: Long?,
    val descricao: String?,
    val valor: Double?
)

// Bom: so nullable quando faz sentido
class Pedido(
    val id: Long,
    val descricao: String,
    val valor: Double,
    val cupomDesconto: String? = null // este sim pode ser nulo
)

Quanto menos tipos nullable no seu código, menos verificacoes de nulo você precisa fazer e menor a chance de NullPointerException em tempo de execução.

Prefira Imutabilidade com val

Sempre que possível, use val em vez de var. Propriedades imutáveis são mais faceis de entender, mais seguras em contextos concorrentes é fácilitam o raciocinio sobre o estado do programa.

// Ruim: var quando nao precisa mudar
var nome = "Kotlin Brasil"

// Bom: val quando o valor nao muda
val nome = "Kotlin Brasil"

O mesmo vale para collections. Prefira listOf() e mapOf() (imutáveis) e só use mutableListOf() quando realmente precisar modificar a colecao.

// Bom: colecao imutavel
val linguagens = listOf("Kotlin", "Java", "Scala")

// So quando necessario
val resultados = mutableListOf<String>()
resultados.add("Primeiro resultado")

Sealed Classes para Modelar Estado

Quando você precisa representar um conjunto finito de estados — como o resultado de uma chamada de rede ou o estado de uma tela — sealed classes são a escolha ideal.

sealed class UiState<out T> {
    object Carregando : UiState<Nothing>()
    data class Sucesso<T>(val dados: T) : UiState<T>()
    data class Erro(val mensagem: String) : UiState<Nothing>()
}

// Uso no ViewModel
fun buscarUsuarios(): UiState<List<Usuario>> {
    return try {
        val usuarios = repositorio.buscarTodos()
        UiState.Sucesso(usuarios)
    } catch (e: Exception) {
        UiState.Erro(e.message ?: "Erro desconhecido")
    }
}

// Tratamento exaustivo na UI
when (val estado = viewModel.estado) {
    is UiState.Carregando -> mostrarLoading()
    is UiState.Sucesso -> mostrarLista(estado.dados)
    is UiState.Erro -> mostrarErro(estado.mensagem)
}

Essa abordagem e amplamente usada em projetos com MVVM e Jetpack Compose, e deixa o fluxo de dados muito mais previsivel.

Outras Boas Práticas Rápidas

Antes de encerrar, vale listar mais algumas práticas que fazem diferenca no dia a dia:

  • Use string templates: prefira "Ola, $nome" em vez de "Ola, " + nome.
  • Use named arguments em funções com muitos parametros: criarUsuario(nome = "Ana", idade = 28) e muito mais claro do que criarUsuario("Ana", 28).
  • Use default parameters em vez de sobrecargas de função:
fun conectar(
    host: String,
    porta: Int = 8080,
    usarSsl: Boolean = true
) { ... }
  • Prefira extension functions para adicionar comportamento a classes existentes em vez de criar classes utilitarias com métodos estaticos.
  • Escreva testes desde o inicio. Código bem escrito e código testavel.

Conclusão

Boas práticas não são regras rigidas gravadas em pedra — são diretrizes que evoluem com a comunidade e com a própria linguagem. O mais importante e manter consistencia dentro do projeto e priorizar a legibilidade. Ferramentas como ktlint e detekt ajudam a automatizar parte desse trabalho, mas o bom senso do desenvolvedor continua sendo insubstituivel.

Se você esta comecando agora com Kotlin, não tente aplicar todas essas práticas de uma vez. Va incorporando aos poucos, comecando pelas mais basicas como nomenclatura e uso de val, e depois avance para padrões mais sofisticados como sealed classes e scope functions. Com o tempo, escrever código Kotlin idiomatico vai se tornar natural.