Introdução: Por que até desenvolvedores inteligentes tropeçam no Kotlin
\n\nKotlin é uma linguagem elegante e poderosa que conquistou o coração de milhões de desenvolvedores. Mas sua concisão e flexibilidade escondem armadilhas, especialmente para iniciantes vindos de Java ou outras linguagens. Muitos acham que, como Kotlin é "mais compreensível" que Java, haverá menos erros. Na prática, é o contrário: a falta de compreensão da filosofia da linguagem leva a bugs difíceis de detectar.
\n\nNeste artigo, analisaremos os principais erros de desenvolvedores Kotlin iniciantes — desde a simples ignorância do null safety até o uso incorreto de coroutines. Cada erro será ilustrado com um exemplo de código e uma dica prática de como evitá-lo.
1. Null Safety: quando "?" salva e quando não
\n\n1.1. Ignorar tipos nullable
\nO erro mais comum é usar !! (not-null assertion) onde seria possível usar chamadas seguras. Iniciantes frequentemente escrevem:
fun printLength(str: String?) {\n println(str!!.length) // Lançará NPE se str == null\n}\n\nA abordagem correta é usar ?.let ou o operador elvis ?::
fun printLength(str: String?) {\n str?.let { println(it.length) } ?: println("String é null")\n}\n\n1.2. Esquecer do Smart Cast
\nKotlin converte tipos automaticamente após a verificação de null, mas iniciantes não percebem isso:
\n\nfun process(value: Any?) {\n if (value is String) {\n // value já é String, não precisa de cast!\n println(value.length) // Funciona sem (value as String).length\n }\n}\n\nDica: Confie no compilador. Use !! apenas se tiver 100% de certeza de que o valor não é null e estiver preparado para uma falha em caso de erro.
2. Sealed Class e When: armadilhas da exaustividade
\n\n2.1. Usar o else com sealed class
\nSealed classes foram criadas para que o compilador verifique se todas as opções foram tratadas. Mas iniciantes frequentemente adicionam else, matando toda a mágica:
sealed class Result {\n data class Success(val data: String) : Result()\n data class Error(val message: String) : Result()\n}\n\nfun handleResult(result: Result) {\n when (result) {\n is Result.Success -> println(result.data)\n is Result.Error -> println(result.message)\n else -> {} // Erro! Se adicionar uma nova subclasse, o compilador não avisará\n }\n}\n\nO correto é não usar else:
\n\nfun handleResult(result: Result) {\n when (result) {\n is Result.Success -> println(result.data)\n is Result.Error -> println(result.message)\n } // O compilador emitirá um erro se nem todos os ramos forem cobertos\n}\n\n2.2. Confundir sealed class com enum
\nIniciantes frequentemente escolhem enum onde precisam de sealed class. Lembre-se: enum é para um conjunto finito de constantes, sealed class é para uma hierarquia com dados diferentes em cada subclasse.
3. Scope Functions: apply, let, run, with, also — como não se confundir
\n\n3.1. Misturar contexto: this vs it
\nA confusão mais comum é quando o iniciante usa apply (contexto — this) para operações onde é necessário let (contexto — it):
data class User(var name: String, var age: Int)\n\nfun initUser(): User {\n return User("Anônimo", 0).apply {\n // this — é User\n name = "João"\n age = 25\n }\n}\n\nfun printUserInfo(user: User?) {\n user?.let { \n // it — é user\n println("Nome: ${it.name}, Idade: ${it.age}")\n }\n}\n\n3.2. Cadeias de scope functions sem sentido
\nIniciantes adoram escrever cadeias como:
\n\nlist?.let { it.filter { it > 5 }.also { println(it) } }