Tratamento de Erros em Kotlin: try/catch e Técnicas Avançadas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: Por que o try/catch padrão não é suficiente?

\n\n

O desenvolvimento em Kotlin, especialmente no contexto de aplicações servidoras ou Android, frequentemente enfrenta a necessidade de um tratamento de erros robusto. O bloco padrão try/catch é a base, mas na prática ele leva a uma lógica "espalhada" e dificulta os testes. Em um nível avançado, não queremos apenas capturar exceções, mas sim projetar o sistema de forma que os erros sejam parte do tipo de dado, e não um fluxo de controle inesperado.

\n\n

Neste artigo, vamos explorar como ir além do try/catch e usar abordagens funcionais: Result, Either (de bibliotecas) e sealed class. Você aprenderá como tornar o código previsível, type-safe e fácil de manter.

\n\n

1. try/catch clássico: quando ainda é útil?

\n\n

Não devemos abandonar completamente o try/catch. Ele é indispensável para trabalhar com código legado, bibliotecas externas (por exemplo, ao fazer parsing de JSON) e chamadas de sistema (entrada/saída). No entanto, existem nuances importantes.

\n\n

Estrutura correta e recursos

\n

Em Kotlin, ao contrário do Java, não há exceções verificadas, mas há a elegante construção use para fechamento automático de recursos:

\n\n
fun readFile(path: String): String? = try {\n    BufferedReader(FileReader(path)).use { it.readText() }\n} catch (e: IOException) {\n    println("Erro ao ler arquivo: ${e.message}")\n    null\n}
\n\n

Aqui, use garante o fechamento do arquivo, mesmo que uma exceção ocorra internamente. Esta é uma substituição elegante para o try-with-resources.

\n\n

Problema: perda de contexto

\n

A principal desvantagem do try/catch é que ele não informa ao código chamador que a função pode "falhar". A exceção é um efeito colateral oculto. A chamada readFile() pode retornar null ou lançar uma IOException, mas a assinatura da função não reflete isso. Isso leva a erros em tempo de execução.

\n\n

A solução é migrar para uma representação explícita de erros.

\n\n

2. Result: a mônada embutida para sucesso ou falha

\n\n

Kotlin fornece a classe padrão Result<T>. Ela é uma estrutura algébrica simples: ou sucesso (valor T), ou falha (Throwable). Isso torna o tratamento de erros parte do sistema de tipos.

\n\n

Uso básico

\n
fun divisaoArriscada(a: Int, b: Int): Result<Int> = runCatching {\n    a / b\n}\n\nfun main() {\n    val resultado = divisaoArriscada(10, 0)\n    resultado.onSuccess { println("Resultado: $it") }\n             .onFailure { println("Erro: ${it.message}") }\n}
\n\n

runCatching é um invólucro sobre try/catch que retorna um Result. Os métodos onSuccess e onFailure permitem reagir ao resultado sem interromper a cadeia de chamadas.

\n\n

Transformação e encadeamento

\n

Você pode transformar o resultado sem se preocupar com exceções dentro do map:

\n\n
val resultadoFinal = divisaoArriscada(10, 2)\n    .map { it * 2 }          // se sucesso — multiplica\n    .recover { erro ->      // se erro — retorna um valor padrão\n        println("Usando valor padrão devido a: ${erro.message}")\n        0\n    }\nprintln(resultadoFinal.getOrDefault(-1)) // 10
\n\n

Importante: Result não foi projetado para ser armazenado em coleções ou transmitido entre threads sem processamento adicional. Para esses fins, sealed class ou bibliotecas como Arrow são mais adequadas.

\n\n

3. Either: diferenciando o tipo de erro

\n\n

A classe Result captura apenas Throwable. Mas frequentemente queremos diferenciar tipos de erros: "não encontrado", "permissão insuficiente", "erro de validação". É aqui que entra o Either<L, R> de bibliotecas funcionais (por exemplo, Arrow ou implementações próprias

Blogs

Book Recommendations