Dart: Tratamento de Exceções em Dart
Última atualização: 2026-08-26
O tratamento de exceções é a rede de segurança do seu código — sem ele, um único erro pode derrubar todo o sistema.
1. O que Você Aprenderá
- Diferença semântica entre Exception e Error
- Sintaxe completa de try / on / catch / finally
- Classes de exceção personalizadas e design de códigos de erro
- rethrow e encadeamento de exceções
- Cenário do Bob: Tratamento de exceções de análise de arquivos e timeout de rede no DataPipeline
2. A História Real de um Desenvolvedor
(1) O Problema: Exceções Não Tratadas Interrompem o Processamento em Lote
O DataPipeline do Bob, ao processar milhões de pedidos, travou devido a uma linha CSV malformada causando uma FormatException. Todos os 800.000 registros processados foram perdidos, exigindo uma nova execução completa. Pior, a mensagem de erro mostrava apenas "FormatException" sem número de linha ou contexto, e Bob levou 4 horas para localizar o problema.
(2) A Solução com Tratamento de Exceções
Usar try-on-catch-finally para capturar exceções específicas, classes de exceção personalizadas para carregar informações de contexto, e finally para garantir a limpeza de recursos.
try {
final records = await parseCsvFile(path);
await processRecords(records);
} on FormatException catch (e) {
log.error('CSV parse error: ${e.message} at line ${e.offset}');
// Pular registros malformados, continuar processamento
} on TimeoutException {
log.error('API timeout, retrying...');
await retryWithBackoff();
} finally {
await closeResources();
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
(3) Os Benefícios
- Um erro em um único registro não causa mais a falha de todo o lote
- Exceções personalizadas carregam contexto como números de linha e campos, reduzindo o tempo de localização de 4 horas para 5 minutos
finallygarante que identificadores de arquivos e conexões de rede sejam sempre liberados
3. Exception vs Error
(1) Diferença Semântica
flowchart TD A[Throwable] --> B[Error<br/>Recuperável: NÃO] A --> C[Exception<br/>Recuperável: SIM] B --> B1[OutOfMemoryError] B --> B2[StackOverflowError] C --> C1[FormatException] C --> C2[TimeoutException] C --> C3[IOException] C --> C4[Custom Exception]
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
| Aspecto | Error | Exception |
|---|---|---|
| Recuperabilidade | Não recuperável | Recuperável |
| Deve ser capturado | Não | Sim |
| Produzido por | VM / Runtime | Código da aplicação |
| Exemplo | StackOverflowError | FormatException |
4. try / on / catch / finally
(1) Sintaxe Completa
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:try-catch básico
void main() {
try {
final result = int.parse('abc');
print(result);
} on FormatException catch (e) {
print('Format error: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:Diferença entre on e catch
void main() {
// on - captura tipo específico, sem acesso ao objeto de exceção
try {
int.parse('not a number');
} on FormatException {
print('Caught FormatException (no details needed)');
}
// on + catch - captura tipo específico COM acesso à exceção
try {
int.parse('not a number');
} on FormatException catch (e) {
print('Caught: ${e.message}');
}
// catch com stack trace
try {
int.parse('not a number');
} on FormatException catch (e, stackTrace) {
print('Error: $e');
print('Stack: $stackTrace');
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:O bloco finally
import 'dart:io';
void main() async {
File? file;
try {
file = File('orders.csv');
final content = await file.readAsString();
print('Read ${content.length} characters');
} on FileSystemException catch (e) {
print('File error: ${e.message}');
} finally {
// Sempre executado - mesmo com return ou throw
print('Cleanup: file handle released');
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
| Cláusula | Propósito | Pode ter múltiplos | Ordem |
|---|---|---|---|
try |
Envolve código que pode lançar exceção | 1 | Primeiro |
on Type |
Captura um tipo específico | Múltiplos | Após try |
catch (e) |
Captura qualquer exceção | 1 | Após on |
finally |
Sempre executado | 1 | Último |
5. Classes de Exceção Personalizadas
(1) Princípios de Design de Classes de Exceção
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:Classes de exceção personalizadas
// Exceção base para DataPipeline
class PipelineException implements Exception {
final String message;
final String? source;
final int? lineNumber;
PipelineException(this.message, {this.source, this.lineNumber});
@override
String toString() => 'PipelineException: $message'
'${source != null ? " (source: $source)" : ""}'
'${lineNumber != null ? " at line $lineNumber" : ""}';
}
// Tipos específicos de exceção
class DataFormatException extends PipelineException {
final String fieldName;
final String invalidValue;
DataFormatException({
required this.fieldName,
required this.invalidValue,
required super.message,
super.source,
super.lineNumber,
});
@override
String toString() => 'DataFormatException: $message '
'(field: $fieldName, value: "$invalidValue")';
}
class NetworkTimeoutException extends PipelineException {
final Duration timeout;
final String endpoint;
NetworkTimeoutException({
required this.timeout,
required this.endpoint,
super.message = 'Request timed out',
}) : super(message);
@override
String toString() => 'NetworkTimeout: ${timeout.inSeconds}s '
'timeout on $endpoint';
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:Design de códigos de erro
enum ErrorCode {
fileNotFound('E001', 'File not found'),
invalidFormat('E002', 'Invalid data format'),
networkTimeout('E003', 'Network request timed out'),
authFailed('E004', 'Authentication failed'),
rateLimitExceeded('E005', 'Rate limit exceeded');
final String code;
final String description;
const ErrorCode(this.code, this.description);
}
class CodedException extends PipelineException {
final ErrorCode errorCode;
CodedException(this.errorCode, {String? detail})
: super('${errorCode.code}: ${errorCode.description}'
'${detail != null ? " - $detail" : ""}');
@override
String toString() => '[$errorCode] $message';
}
void main() {
try {
throw CodedException(ErrorCode.invalidFormat, detail: 'amount field is not a number');
} on CodedException catch (e) {
print(e); // [ErrorCode.invalidFormat] E002: Invalid data format - amount field is not a number
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
6. rethrow e Encadeamento de Exceções
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:rethrow
double parseAmount(String input) {
try {
return double.parse(input);
} on FormatException catch (e) {
// Registrar e relançar - não engolir a exceção
print('Failed to parse amount: "$input"');
rethrow; // Preserva o stack trace original
}
}
void main() {
try {
final amount = parseAmount('not_a_number');
} on FormatException {
print('Caught rethrown exception');
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:Encadeamento de exceções
class ChainedException implements Exception {
final String message;
final Exception? innerException;
ChainedException(this.message, {this.innerException});
@override
String toString() {
var result = 'ChainedException: $message';
if (innerException != null) {
result += '\n Caused by: $innerException';
}
return result;
}
}
Future``<double>`` fetchOrderAmount(String orderId) async {
try {
// Simular chamada de API
throw FormatException('Invalid JSON response');
} on FormatException catch (e) {
throw ChainedException(
'Failed to fetch order $orderId',
innerException: e,
);
}
}
void main() async {
try {
await fetchOrderAmount('ORD-001');
} on ChainedException catch (e) {
print(e);
}
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
7. Cenário do Bob: Tratamento de Exceções no DataPipeline
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
:Fluxo completo de tratamento de exceções
import 'dart:async';
// Exceções personalizadas
class PipelineException implements Exception {
final String message;
PipelineException(this.message);
@override
String toString() => 'PipelineException: $message';
}
class CsvParseException extends PipelineException {
final int lineNumber;
CsvParseException(String message, this.lineNumber) : super(message);
@override
String toString() => 'CsvParseException: $message (line $lineNumber)';
}
// Parser CSV seguro com tratamento de exceções
List<Map<String, String>> parseCsv(String content) {
final lines = content.split('\n');
if (lines.isEmpty) throw PipelineException('Empty CSV content');
final headers = lines[0].split(',');
final records = <Map<String, String>>[];
for (var i = 1; i < lines.length; i++) {
final line = lines[i].trim();
if (line.isEmpty) continue;
try {
final values = line.split(',');
if (values.length != headers.length) {
throw CsvParseException(
'Column count mismatch: expected ${headers.length}, got ${values.length}',
i + 1,
);
}
final record = <String, String>{};
for (var j = 0; j < headers.length; j++) {
record[headers[j].trim()] = values[j].trim();
}
records.add(record);
} on CsvParseException {
rethrow;
} catch (e) {
throw CsvParseException('Unexpected error: $e', i + 1);
}
}
return records;
}
Future``<void>`` processData(String csvContent) async {
List<Map<String, String>>? records;
try {
records = parseCsv(csvContent);
print('Parsed ${records.length} records');
// Simular chamada de API com timeout
await Future.delayed(const Duration(seconds: 1));
print('Data submitted successfully');
} on CsvParseException catch (e) {
print('Parse error: $e - skipping malformed records');
} on TimeoutException catch (e) {
print('Network timeout: $e - will retry later');
} on PipelineException catch (e) {
print('Pipeline error: $e');
} finally {
print('Cleanup: resources released');
}
}
void main() async {
final csv = 'id,amount,status\nORD-001,1500,completed\nORD-002,50,pending\nORD-003,bad_data,completed';
await processData(csv);
print('\n--- Test with malformed CSV ---');
final badCsv = 'id,amount\nORD-001,1500,completed'; // Wrong column count
await processData(badCsv);
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
8. Exemplo Completo: Processamento de Dados Robusto no DataPipeline
// ============================================
// DataPipeline Robust Data Processing
// Tratamento de exceções completo com exceções personalizadas
// ============================================
import 'dart:async';
// Códigos de erro
enum PipelineError {
fileNotFound('E001', 'File not found'),
invalidFormat('E002', 'Invalid data format'),
networkTimeout('E003', 'Network timeout'),
validationFailed('E004', 'Validation failed');
final String code;
final String label;
const PipelineError(this.code, this.label);
}
class PipelineException implements Exception {
final PipelineError error;
final String detail;
final Exception? cause;
PipelineException(this.error, {this.detail = '', this.cause});
@override
String toString() => '[${error.code}] ${error.label}'
'${detail.isNotEmpty ? ": $detail" : ""}'
'${cause != null ? " (caused by: $cause)" : ""}';
}
// Pedido com validação
class Order {
final String id;
final double amount;
final String status;
Order({required this.id, required this.amount, required this.status}) {
if (id.isEmpty) {
throw PipelineException(PipelineError.validationFailed, detail: 'Order ID is empty');
}
if (amount <= 0) {
throw PipelineException(PipelineError.validationFailed, detail: 'Amount must be positive: $amount');
}
}
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}
// Parser de pedidos robusto
class OrderParser {
final List``<PipelineException>`` _errors = [];
int parsed = 0;
int skipped = 0;
List``<PipelineException>`` get errors => List.unmodifiable(_errors);
Order? tryParse(Map<String, dynamic> data) {
try {
final order = Order(
id: (data['id'] ?? '') as String,
amount: (data['amount'] as num).toDouble(),
status: (data['status'] ?? 'unknown') as String,
);
parsed++;
return order;
} on PipelineException catch (e) {
_errors.add(e);
skipped++;
return null;
} on TypeError catch (e) {
_errors.add(PipelineException(
PipelineError.invalidFormat,
detail: 'Type mismatch in record: $e',
));
skipped++;
return null;
}
}
void printReport() {
print('Parsed: $parsed, Skipped: $skipped');
if (_errors.isNotEmpty) {
print('Errors:');
for (final e in _errors) {
print(' $e');
}
}
}
}
void main() {
final rawData = <Map<String, dynamic>>[
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': '', 'amount': 500.0, 'status': 'pending'}, // Inválido: ID vazio
{'id': 'ORD-003', 'amount': -50.0, 'status': 'completed'}, // Inválido: valor negativo
{'id': 'ORD-004', 'amount': 'not_a_number', 'status': 'pending'}, // Inválido: tipo errado
{'id': 'ORD-005', 'amount': 3200.0, 'status': 'completed'},
];
final parser = OrderParser();
final validOrders = ``<Order>``[];
for (final data in rawData) {
final order = parser.tryParse(data);
if (order != null) validOrders.add(order);
}
print('=== DataPipeline Processing Report ===');
parser.printReport();
print('\nValid Orders:');
for (final order in validOrders) {
print(' $order');
}
final totalRevenue = validOrders.fold``<double>``(0, (s, o) => s + o.amount);
print('\nTotal Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
Saída:
=== DataPipeline Processing Report ===
Parsed: 3, Skipped: 2
Errors:
[E004] Validation failed: Order ID is empty
[E004] Validation failed: Amount must be positive: -50.0
[E002] Invalid data format: Type mismatch in record: ...
Valid Orders:
Order(ORD-001, $1500.00, completed)
Order(ORD-005, $3200.00, completed)
Total Revenue: $4700.00 USD
❓ Perguntas Frequentes
P: O Dart possui exceções verificadas (checked exceptions)? R: Não. Todas as exceções em Dart são não verificadas; o compilador não exige declaração ou captura. Isso oferece flexibilidade, mas exige que os desenvolvedores tratem exceções conscientemente.
P: Qual é a diferença entre
onecatch? R:on Typecaptura um tipo específico de exceção sem vincular uma variável (a menos quecatchseja adicionado).catch (e)captura qualquer exceção e a vincula a uma variável. Normalmente, usa-se a combinaçãoon Type catch (e).
P: Qual é a utilidade de
catcherethrow? R:catchpermite registrar logs, realizar limpeza, etc., após capturar uma exceção. Depois, userethrowpara relançar a mesma exceção para que as camadas superiores possam continuar tratando-a.rethrowpreserva o stack trace original, o que é melhor quethrow e.
P: Quando o bloco
finallyé executado? R: O blocofinallysempre executa, independentemente de uma exceção ser lançada no blocotry, de ser capturada, ou de haver uma instruçãoreturn. A única exceção é se o programa for encerrado (por exemplo, via SIGKILL).
P: O que as exceções personalizadas devem herdar? R: Recomenda-se
implement Exceptionem vez deextend Exception.implementsé mais flexível, não sendo restrito pela herança única. Essa também é a abordagem recomendada pelas diretrizes oficiais do Dart.
P: O tratamento de exceções afeta o desempenho? R: O bloco try-catch em si tem sobrecarga quase zero (a Dart VM não adiciona instruções extras dentro de blocos try). No entanto, a criação e o lançamento de exceções têm um custo e não devem ser usados como mecanismo normal de controle de fluxo.
P: Como evitar engolir exceções? R: Um bloco
catchvazio é um code smell. Pelo menos registre a exceção no log ou userethrow. Se precisar ignorá-la, usecatch (_) {}e adicione um comentário explicando o motivo.
📖 Resumo
- Exceções são recuperáveis e devem ser capturadas; Errors não são recuperáveis e não devem ser capturados
- Sintaxe completa try-on-catch-finally:
oncaptura por tipo,catchvincula uma variável,finallysempre executa - Exceções personalizadas
implement Exception, carregando contexto (número da linha, campo, código de erro) rethrowpreserva o stack trace original; o encadeamento de exceções ajuda a rastrear a causa raiz- O DataPipeline usa o padrão
tryParse: a falha de um único registro não afeta todo o processamento em lote
📝 Exercícios
- Básico (Dificuldade ⭐): Escreva uma função
safeParseInt(String s)que useint.parsedentro de um bloco try-catch. Se a análise falhar, retorne0em vez de lançar uma exceção. Teste com 3 casos. - Intermediário (Dificuldade ⭐⭐): Defina uma classe de exceção personalizada
DataFormatExceptionque carregue o nome do campo, valor inválido e número da linha. Escreva uma função de análise de CSV que lance essa exceção em erros de formato, e capture-a no local da chamada para imprimir informações detalhadas. - Desafio (Dificuldade ⭐⭐⭐): Implemente uma função de requisição HTTP com mecanismo de retry que suporte um número customizado de tentativas e uma estratégia de backoff. Tente novamente em
TimeoutException, e após exceder a contagem de retries, lance uma exceção agregada contendo todas as exceções individuais de cada tentativa.