Tratamento Avançado de Erros em C#: do básico try/catch aos padrões industriais
\n\nTratamento de erros não é apenas capturar exceções, mas a arte de projetar sistemas confiáveis. Em C# (.NET), desenvolvedores frequentemente se limitam à construção try/catch, mas a plataforma oferece mecanismos muito mais poderosos. Neste artigo, exploraremos técnicas avançadas: desde filtros de exceção até padrões de retry e passagem de contexto de erro.
1. Filtros de Exceção: precisão em vez de placeholders
\n\nFiltros de Exceção (Exception Filters), introduzidos no C# 6.0, permitem especificar uma condição para entrar no bloco catch sem relançar a exceção. Isso é muito mais limpo do que a abordagem clássica com if dentro do bloco.
try\n{\n // Código que pode lançar uma exceção\n var result = ProcessData(input);\n}\ncatch (CustomException ex) when (ex.ErrorCode == 1001)\n{\n // Processa apenas erros com código 1001\n Log.Error($"Erro crítico: {ex.Message}");\n throw; // Relança adiante, se necessário\n}\ncatch (CustomException ex) when (ex.IsRecoverable)\n{\n // Erro recuperável — registra e continua\n Log.Warning($"Erro recuperável: {ex.Message}");\n RetryOperation();\n}\n\nVantagens:
\n- \n
- Não requer um
throwadicional para filtragem. \n - Melhora a legibilidade e o desempenho (a pilha de chamadas não é corrompida). \n
- Permite combinar vários blocos
catchcom diferentes condições. \n
2. Padrão "Retry" e Polly
\n\nNem todos os erros são fatais. Para falhas temporárias (problemas de rede, bloqueios de BD), use o padrão de tentativas repetidas. Os recursos nativos do C# são limitados, portanto, recomenda-se a biblioteca Polly.
\n\nusing Polly;\nusing Polly.Retry;\n\n// Define a política de tentativas repetidas\nAsyncRetryPolicy retryPolicy = Policy\n .Handle<HttpRequestException>() // Processa apenas erros HTTP\n .WaitAndRetryAsync(\n 3, // Número de tentativas\n retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // Atraso exponencial\n onRetry: (exception, timeSpan, retryCount, context) =>\n {\n Log.Warning($"Tentativa {retryCount} falhou. Repetindo em {timeSpan.Seconds} seg.");\n });\n\n// Aplica a política\nawait retryPolicy.ExecuteAsync(async () =>\n{\n var response = await httpClient.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n});\n\nImportante: Não use retries infinitos. Sempre defina um número máximo de tentativas e use Circuit Breaker para proteger o sistema contra falhas em cascata.
\n\n3. Passagem Correta de Exceções: throw vs throw ex
\n\nUm dos erros mais comuns é a perda da pilha de chamadas. A diferença entre throw e throw ex é crítica para a depuração.
try\n{\n // ...\n}\ncatch (Exception ex)\n{\n // INCORRETO: perde a pilha original\n // throw ex; \n\n // CORRETO: preserva a pilha\n throw;\n\n // OU: adiciona contexto sem perder o original\n // throw new ApplicationException("Erro no módulo X", ex);\n}\n\nSe precisar adicionar contexto, use ExceptionDispatchInfo (do namespace System.Runtime.ExceptionServices) para preservar precisamente a pilha ao relançar entre threads.
ExceptionDispatchInfo.Capture(exception).Throw(); // Preserva a pilha original\n\n4. Exceções Personalizadas e Contexto de Erro
\n\nExceções padrão geralmente não carregam contexto de negócio. Crie suas próprias classes de exceção com propriedades adicionais.
\n\npublic class PaymentProcessingException : Exception\n{\n public int OrderId { get; }\n public dec