Padrões de Tratamento de Erros em Python: Limpo, Previsível e Seguro
Excerpt:** Lidar bem com erros vai além de capturar exceções. Esses padrões ajudam a manter seu código Python robusto, legível e mais fácil de depurar em sistemas reais.
Por Que o Tratamento de Erros Importa
O tratamento de erros define como sua aplicação se comporta sob pressão.
Um sistema com tratamento de exceções inadequado pode:
- Esconder bugs
- Quebrar inesperadamente
- Falhar silenciosamente
- Produzir resultados enganosos
O verdadeiro objetivo do tratamento de erros é clareza.
Você deve sempre saber o que falhou, onde falhou e por que falhou.
O Anti-Padrão Comum
Muitos desenvolvedores começam assim:
try:
process_data()
except:
pass
Isso ignora silenciosamente qualquer exceção.
Remove visibilidade.
Remove capacidade de depuração.
Remove confiança.
Capturar tudo e não fazer nada quase sempre é pior do que deixar o programa falhar.
Padrão 1: Seja Específico nas Exceções
Sempre capture apenas o que você espera.
try:
value = int("abc")
except ValueError as e:
print(f"Valor inválido: {e}")
Capturar Exception de forma ampla deve ficar restrito a camadas de fronteira, como:
- Entrypoints de CLI
- Handlers de requisições web
- Workers assíncronos
Dentro da lógica central, seja explícito.
Padrão 2: Use Exceções Personalizadas
Exceções personalizadas comunicam intenção.
class DataProcessingError(Exception):
"""Disparada quando uma etapa de processamento falha."""
pass
def load_data(path):
if not path.endswith(".csv"):
raise DataProcessingError("Apenas arquivos CSV são suportados.")
Depois:
try:
load_data("data.json")
except DataProcessingError as e:
print(f"Erro ao carregar dados: {e}")
Agora a falha é significativa, não genérica.
Isso é especialmente importante em bibliotecas e APIs.
Padrão 3: Falhar Rápido vs Falhar com Segurança
Nem todo erro deve ser tratado da mesma forma.
Algumas situações exigem falha imediata:
if not config:
raise RuntimeError("Configuração é obrigatória.")
Outras permitem degradação graciosa:
def read_config(path="config.yaml"):
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
logger.warning("Configuração não encontrada. Usando padrão.")
return "{}"
Bom tratamento de erros é escolher intencionalmente entre falhar rápido e falhar com segurança.
Padrão 4: Registre Erros em Vez de Usar Print
Não dependa de print() em sistemas de produção.
Use logging estruturado:
from loguru import logger
def process_file(path):
try:
...
except FileNotFoundError:
logger.error(f"Arquivo não encontrado: {path}")
except Exception:
logger.exception("Falha inesperada")
logger.exception() inclui automaticamente o traceback.
Logging torna falhas visíveis sem derrubar todo o sistema.
Padrão 5: Wrappers de Execução Segura
Em pipelines e scripts de automação, helpers podem reduzir repetição.
def safe_execute(fn, *args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as e:
logger.error(f"Execução falhou: {e}")
return None
Funciona bem em camadas de orquestração, não na lógica central.
Use com cuidado.
Padrão 6: Relevantar com Contexto
Às vezes você quer capturar uma exceção, adicionar contexto e relançá-la.
try:
result = transform(data)
except ValueError as e:
raise DataProcessingError("Falha na transformação") from e
O uso de from e preserva o traceback original enquanto adiciona significado de nível mais alto.
É um dos padrões mais limpos para sistemas em camadas.
Consideração Final
Bom tratamento de erros não é sobre silenciar falhas.
É sobre tornar falhas:
- Previsíveis
- Visíveis
- Ações possíveis
Exceções específicas, tipos personalizados, logging estruturado e lógica de fallback intencional separam scripts frágeis de sistemas confiáveis.
Tratamento claro de erros é um dos sinais mais fortes de código Python maduro.