Tratamento de erros em C#: do try/catch às técnicas profissionais
No mundo do desenvolvimento industrial em C# e .NET, o tratamento de erros não é apenas uma construção try/catch. É uma filosofia completa que define a confiabilidade, legibilidade e tolerância a falhas do seu código. Em um nível avançado, o desenvolvedor não deve apenas capturar exceções, mas também projetá-las, filtrá-las, registrá-las e restaurar o estado da aplicação de forma competente. Neste artigo, abordaremos os principais aspectos do tratamento de erros em C# que o ajudarão a escrever um código mais profissional e de maior qualidade.
1. Aprofundando em try/catch/finally: gerenciamento de recursos e contexto
A construção básica try-catch-finally é familiar a todos, mas vamos analisá-la de um ângulo diferente. O principal objetivo do bloco finally é garantir a liberação de recursos, mesmo que ocorra uma exceção. Nas versões modernas do C#, usa-se mais frequentemente using para isso, mas o finally continua indispensável ao trabalhar com recursos não gerenciados ou lógica de encerramento complexa.
public void ProcessFile(string path){ FileStream? fileStream = null; try { fileStream = File.OpenRead(path); // Lógica de processamento do arquivo } catch (FileNotFoundException ex) { // Tratamento específico: arquivo não encontrado Log.Error($"Arquivo {path} não encontrado: {ex.Message}"); throw; // Relança a exceção, preservando a pilha de chamadas } catch (UnauthorizedAccessException) { Log.Warn($"Sem acesso ao arquivo: {path}"); // Pode executar uma ação alternativa sem relançar a exceção return; } finally { // Liberação garantida do recurso fileStream?.Dispose(); }}Observe o uso de throw; (sem argumento) — este é um ponto crucial. Ao contrário de throw ex;, a construção throw; preserva a pilha de chamadas original, o que é fundamental para a depuração. Nunca use throw ex; em projetos reais, pois isso destrói a informação sobre o local de origem do erro.
2. Exceções personalizadas: projetando um modelo de domínio de erros
Em aplicações grandes, as exceções padrão do .NET muitas vezes não são suficientes. Criar suas próprias classes de exceção permite transmitir com precisão o contexto do erro e simplifica o tratamento nos níveis superiores. Uma boa exceção personalizada deve ser serializável, conter uma mensagem significativa e carregar informações adicionais (por exemplo, o identificador da entidade).
[Serializable]public class OrderProcessingException : Exception{ public int OrderId { get; } public string FaultyStep { get; }
public OrderProcessingException(int orderId, string faultyStep, string message) : base(message) { OrderId = orderId; FaultyStep = faultyStep; }
public OrderProcessingException(int orderId, string faultyStep, string message, Exception inner) : base(message, inner) { OrderId = orderId; FaultyStep = faultyStep; }
// Construtor para desserialização protected OrderProcessingException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { OrderId = info.GetInt32(nameof(OrderId)); FaultyStep = info.GetString(nameof(FaultyStep)) ?? string.Empty; }
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(OrderId), OrderId); info.AddValue(nameof(FaultyStep), FaultyStep); }}
// Usopublic void ValidateOrder(int orderId){ try {