Tratamento de erros em Kotlin: try/catch e além — de A a Z
O tratamento de erros é uma das tarefas essenciais de qualquer desenvolvedor. Em Kotlin, assim como em outras linguagens modernas, existem várias abordagens para gerenciar situações excepcionais. Neste artigo, abordaremos todos os métodos principais: desde o clássico try/catch até técnicas avançadas com Result, sealed class e Nothing. Você aprenderá a escrever código robusto e legível que lida corretamente com erros.
1. Bloco clássico try/catch/finally
O mecanismo básico de tratamento de exceções em Kotlin é o bloco try/catch. Ele funciona de forma semelhante ao Java, mas com algumas melhorias. Em Kotlin, try é uma expressão que pode retornar um valor.
fun parseNumber(input: String): Int? { return try { input.toInt() } catch (e: NumberFormatException) { println("Erro de parsing: ${e.message}") null // retorna null em caso de erro } finally { println("O bloco finally sempre é executado") }}
fun main() { val result = parseNumber("123abc") println(result) // null}Principais características:
trypode ser usado como expressão — o resultado da última expressão no blocotryoucatchtorna-se o resultado de todo o bloco.- O bloco
finallyé executado em qualquer caso (mesmo se houver exceção oureturn). - Kotlin não possui exceções verificadas (checked exceptions), como em Java — isso simplifica o código.
É importante lembrar que o uso excessivo de try/catch torna o código menos legível. Use-o apenas para situações verdadeiramente excepcionais, e não para controle de fluxo.
2. Tipo Result e abordagem funcional
Para um tratamento de erros mais elegante, Kotlin oferece o tipo Result<T>. É uma sealed class que pode ser Success (com o resultado) ou Failure (com a exceção). É ideal para o estilo funcional.
import kotlin.Result
fun divide(a: Int, b: Int): Result<Int> { return try { Result.success(a / b) } catch (e: ArithmeticException) { Result.failure(e) }}
fun main() { val result = divide(10, 0) // Tratamento com when when (result) { is Result.Success -> println("Resultado: ${result.value}") is Result.Failure -> println("Erro: ${result.exception.message}") } // Ou através de métodos result.onSuccess { value -> println("Sucesso: $value") } .onFailure { e -> println("Falha: ${e.message}") } // Transformação val mapped = result.map { it * 2 } // se for failure, permanece failure println(mapped)}Vantagens do Result:
- Mostra explicitamente que a função pode terminar com erro.
- Permite encadeamento de chamadas (
map,flatMap,recover). - Não requer
try/catchno código chamador.
No entanto, Result não é adequado para armazenamento em coleções ou passagem através de limites de corrotinas — para isso, é melhor usar sealed class.
3. Sealed classes para tratamento detalhado de erros
Quando é necessário tratar diferentes tipos de erros de maneiras distintas, sealed class é a escolha ideal. Você pode criar uma hierarquia de erros e tratá-los em um when com cobertura exaustiva.
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class NetworkError(val code: Int, val message: String) : NetworkResult<Nothing>() data class Timeout(val timeout