27 de set. de 2025·7 min de leitura

Códigos de erro consistentes: uma pequena taxonomia e logs mais seguros

Aprenda a criar códigos de erro consistentes, mapear exceções para mensagens seguras ao usuário e gerar logs suficientes para depurar sem expor segredos.

Códigos de erro consistentes: uma pequena taxonomia e logs mais seguros

Por que erros lançados causam caos para usuários e times

Quando cada parte de um app lança seu próprio erro, as pessoas recebem mensagens diferentes para o mesmo problema. Uma falha no login pode mostrar “Algo deu errado” no mobile, “401” na web e uma stack trace vermelha numa tela de administração. O usuário não aprende nada útil, e sua caixa de suporte se enche de capturas de tela que não batem entre si.

Essa inconsistência é a razão pela qual os usuários relatam problemas como “quebrou” ou “não funcionou”. Eles não conseguem dizer se digitavam a senha errada, perderam a conexão ou encontraram uma indisponibilidade temporária. Sem um sinal estável para compartilhar, eles adivinham, tentam de novo ou vão embora.

Os engenheiros sentem isso também. Se os erros são lançados e exibidos sem tratamento, os logs ficam barulhentos: textos diferentes para a mesma causa raiz, contexto faltando e nenhuma maneira limpa de agrupar incidentes. Um bug pode parecer 20 problemas diferentes, então leva mais tempo para identificar padrões, reproduzir o erro e confirmar a correção.

Texto bruto de erro também cria risco de segurança. Exceções frequentemente incluem detalhes que você nunca deve mostrar aos usuários (ou armazenar em logs em texto claro): chaves secretas, strings de conexão, caminhos internos de arquivos, trechos de SQL ou dados de usuários. Um erro não tratado pode vazar mais do que você espera.

Um sistema simples de códigos de erro consistentes resolve isso. Os usuários veem uma mensagem clara e segura mais um código curto que podem compartilhar. Seu time vê logs estruturados ligados ao mesmo código, então a depuração acelera sem expor dados sensíveis.

O que você está construindo: códigos, mensagens e logs úteis

Você está criando um contrato simples para falhas:

  • Um código estável que identifica o que aconteceu
  • Uma mensagem segura para o usuário que explica o que fazer em seguida
  • Logs que ajudam a corrigir a causa raiz

Um bom código de erro não é uma frase. É um identificador que permanece o mesmo mesmo que você reescreva a mensagem, mude frameworks ou refatore toda a feature. Essa estabilidade é o que torna os códigos úteis para suporte, dashboards e relatórios de bugs.

Mantenha as camadas separadas. Usuários recebem linguagem simples e passos seguintes. Engenheiros recebem contexto detalhado nos logs. Essa separação também reduz o risco de vazamento porque a mensagem para o usuário nunca inclui segredos, traces, erros SQL ou IDs internos.

Exemplo: se o login falha porque o banco de dados está fora do ar, o usuário deve ver “Não conseguimos autenticar você agora. Tente novamente em alguns minutos.” O código pode ser AUTH.SERVICE_UNAVAILABLE. Os logs capturam o timeout do banco de dados, a dependência que falhou e um request ID.

Para uma ferramenta interna pequena, lançar erros simples pode bastar. Quando você tem usuários reais, tickets de suporte ou preocupações de conformidade, essa estrutura compensa rapidamente.

Comece com uma pequena taxonomia de erros que você consiga manter

Uma boa taxonomia é propositalmente chata. Se você não consegue lembrá‑la sem consultar um documento, ela é grande demais. O objetivo é códigos de erro consistentes que digam que tipo de problema ocorreu sem expor como seu código está organizado.

Agrupe por impacto no usuário, não por camadas internas. “DB” é útil porque geralmente significa “tente de novo mais tarde”. “RepositoryError” é só a estrutura de pastas vazando para a API.

Aqui está um conjunto inicial simples (6 grupos) que cobre a maioria dos apps:

GrupoO que pertence aqui (uma frase)Retryable?
AUTHProblemas de login/sessão como credenciais inválidas, tokens expirados ou falta de autenticação.Às vezes (refresh de token), muitas vezes Não
VALIDATIONA requisição está errada: campos faltando, formatos ruins, valores fora do permitido.Não
PAYMENTCobrança, status de assinatura ou recusas do provedor de pagamento.Às vezes (timeouts), muitas vezes Não
DBBanco de dados indisponível, timeouts, deadlocks ou migrações falhas.Frequentemente Sim
INTEGRATIONUm serviço terceiro falhou (email, SMS, mapas, webhooks).Frequentemente Sim
INTERNALQualquer coisa inesperada que deva ser investigada (bugs, nulls, estados impossíveis).Às vezes, por padrão Não

Duas regras mantêm isso administrável:

  1. Cada grupo precisa de uma frase clara “pertence aqui se...”, para que as pessoas não debatam casos de borda por uma hora.
  2. Decida o comportamento de retry no nível do grupo como padrão, e sobrescreva apenas quando for necessário.

Se um usuário clica em “Salvar” e o app não consegue alcançar o banco, isso é DB (retryable) mesmo que o erro tenha vindo de uma biblioteca de acesso a dados. Enquanto isso, “email sem @” é sempre VALIDATION (não retryable), mesmo que tenha sido detectado em camadas profundas do seu código.

Projete códigos de erro que permaneçam estáveis ao longo do tempo

Códigos de erro só ajudam se significarem a mesma coisa semana que vem. Trate‑os como uma API pública: melhore a mensagem e o tratamento, mas mantenha o código estável depois que for lançado.

Escolha um formato legível e curto o suficiente para colar em chats de suporte:

  • Use AREA-NNN (exemplos: AUTH-001, DB-003, PAY-012)
  • Faça o prefixo corresponder à sua taxonomia (AUTH, DB, FILE, RATE, PERM)
  • Reserve faixas numéricas se tiver subsistemas grandes (AUTH-100+ para OAuth, AUTH-200+ para sessões)
  • Nunca reutilize números, mesmo se um problema estiver “resolvido”
  • Mantenha os códigos em uma única fonte de verdade (um arquivo ou tabela no repo)

Decida o que deve permanecer estável entre releases: o código, seu significado (uma frase) e a categoria. O que pode mudar: o texto mostrado ao usuário, os passos sugeridos e os detalhes internos que você coloca nos logs.

Separe também o código do correlation ID. O usuário vê AUTH-001. Seus logs recebem correlation_id=8f3c... mais stack trace e contexto. O suporte pode pedir pelo código e pelo correlation ID sem expor internos sensíveis.

Mapear falhas internas para mensagens seguras ao usuário

Usuários não precisam saber o que quebrou dentro do seu app. Eles precisam saber o que aconteceu, o que fazer em seguida e como obter ajuda se ficarem travados.

Uma mensagem segura para o usuário normalmente precisa de três partes:

  • O que aconteceu (breve)
  • O que fazer em seguida
  • O que compartilhar com o suporte (o código)

Exemplo:

“Falha no login. Tente novamente ou redefina sua senha. Compartilhe este código com o suporte: AUTH-002.”

Mantenha o texto do usuário separado dos detalhes para desenvolvedores. A mensagem para o usuário nunca deve incluir stack traces, nomes de tabelas, paths de endpoints, chaves de API, emails ou tokens brutos. Esses detalhes pertencem apenas aos logs e mesmo nesses casos devem ser sanitizados.

Um exemplo realista: seu banco de dados lança UniqueViolation porque um email já existe. Internamente isso pode incluir detalhes de SQL. Externamente, retorne: “Esse email já está em uso. Tente entrar em vez disso. Compartilhe este código com o suporte: ACC-001.” Faça logs do tipo de exceção e do request ID, mas não grave o email completo.

Mantenha os logs úteis sem vazar dados

Estabilizar antes do lançamento
Refatoramos código espaguete e preparamos deployments para que as correções persistam após o lançamento.

Logs são para você, não para seus usuários. O objetivo é simples: quando algo falha, você consegue encontrar a requisição exata, ver o que quebrou e consertar rápido, sem armazenar senhas, tokens ou dados de clientes no processo.

Um bom padrão é logar três âncoras sempre: o código de erro, um correlation ID e a localização (serviço, módulo, rota ou handler). Isso transforma uma stack trace aleatória em algo pesquisável e agrupável.

Uma checklist simples que funciona para a maioria dos apps:

  • Sempre logue: error_code, correlation_id, caminho da requisição e onde aconteceu (endpoint mais função/módulo).
  • Logue identidade de forma segura: um ID interno de usuário costuma ser suficiente; evite emails, nomes ou endereços IP completos, salvo se necessário.
  • Redija ou faça hash de campos sensíveis: senhas, tokens, chaves de API, cookies, headers de autenticação e segredos de variáveis de ambiente.
  • Use níveis de log com propósito: WARN para falhas esperadas (input ruim, autorização negada), ERROR para quedas inesperadas.
  • Decida retenção e acesso: mantenha logs só pelo tempo necessário e limite quem pode visualizá‑los.

Um padrão de falha comum é o “debug útil” que imprime requisições completas ou variáveis de ambiente. Isso pode vazar segredos em logs e backups. Se você herdou código assim, corrija o logging primeiro. Isso reduz o risco imediatamente, mesmo antes de resolver todo bug.

Passo a passo: implemente erros consistentes no seu app

Comece escolhendo uma única forma de erro que todas as partes do app possam retornar. Isso é o que torna códigos de erro consistentes possíveis mesmo quando falhas vêm de bibliotecas diferentes.

Uma forma simples e prática se parece com isto:

{
  "code": "AUTH_INVALID_CREDENTIALS",
  "message": "Email or password is incorrect.",
  "status": 401,
  "correlationId": "b3f1d2..."
}

Depois implemente em pequenos passos:

  1. Crie um AppError (ou similar) e um helper para construí‑lo. Faça code e status obrigatórios, e mantenha message seguro para o usuário.
  2. Adicione um handler global que capture exceções não tratadas (middleware do servidor, handler do gateway de API ou boundary de erro no app). Ele deve sempre retornar a mesma forma.
  3. Centralize o mapeamento de exceções em um só lugar. Exemplo: mapear uma violação de constraint do banco para CONFLICT_EMAIL_TAKEN em vez de deixar o texto cru vazar.
  4. Padronize códigos de status HTTP para que clientes reajam de forma previsível.
  5. Adicione testes que afirmem tanto o code quanto a message, não apenas o status.

Para códigos de status, mantenha o conjunto pequeno e previsível:

  • 400 para input inválido
  • 401 para não autenticado ou credenciais inválidas
  • 403 para autenticado mas sem permissão
  • 404 para recurso inexistente
  • 409 para conflitos (já existe)

Um cenário realista: um endpoint de login lança “Cannot read properties of undefined” porque o corpo da requisição está ausente. Com um mapper, isso vira INPUT_MISSING_FIELDS com 400 e uma mensagem clara, enquanto os logs mantêm a stack trace vinculada a um correlationId.

Fontes comuns de erro e como classificá‑las

Ir de quebrado a funcionando
A maioria das correções é concluída em 48 a 72 horas após identificarmos os problemas.

A maioria dos apps falha nos mesmos poucos lugares. Se você nomear esses lugares e tratá‑los sempre da mesma forma, terá tickets de suporte mais claros e menos mensagens “quebrou”.

Uma regra útil: classifique pelo que o usuário pode fazer em seguida, não pelo texto exato da exceção. Duas stack traces podem parecer diferentes mas ainda significar “faça login de novo” ou “tente mais tarde”.

Auth e permissões devem separar “precisa fazer login” de “acesso negado”, para que a UI saiba se deve pedir autenticação ou mostrar mensagem de permissão. Input e validação devem retornar códigos que o usuário possa corrigir e, quando possível, apontar o campo sem expor detalhes do servidor.

Falhas de banco e armazenamento devem separar “não encontrado” de “consulta falhou”, porque levam a ações diferentes (mostrar estado vazio vs tentar de novo depois). Serviços externos devem classificar se retry é seguro agora, mais tarde ou nunca. Uploads e arquivos devem ser específicos o suficiente para ajudar o usuário a escolher outro arquivo, mas nunca devem ecoar o conteúdo do arquivo nos logs.

Erros comuns que pioram o tratamento de falhas

A maior parte do tratamento ruim de erros vem de times que otimizam por “fazer funcionar” em vez de “manter suporte”. Alguns hábitos transformam pequenos bugs em horas de adivinhação e podem vazar dados sensíveis.

Erros que prejudicam suporte e confiança

Usar a mensagem humana como o “código” é uma armadilha comum. Mensagens mudam quando você reescreve copy, traduz texto ou adiciona detalhes. O suporte não consegue pesquisar de forma confiável, e clientes não conseguem construir comportamentos estáveis.

Outra armadilha é começar com um catálogo enorme. Se você criar 200+ códigos no dia 1, ninguém os mantém e as pessoas inventam novos códigos aleatoriamente. Um conjunto pequeno e claro que você reutiliza vence uma lista longa que você ignora.

Texto cru de exceção nunca deve alcançar usuários ou clientes de API. Frequentemente contém stack traces, nomes de tabela, caminhos de arquivo ou pistas que ajudam um atacante.

Logar corpos de requisição completos é outro risco evitável. É uma forma fácil de capturar senhas, tokens, chaves de API ou dados pessoais. Logs devem ajudar a depurar sem se tornar uma violação de dados.

Por fim, não misture tipos diferentes de falha. Tratar “não encontrado” como “erro do servidor” esconde problemas reais de confiabilidade e torna alertas barulhentos.

Checklist rápido antes de lançar

Antes da release, faça uma passada que verifique a experiência do usuário, o contrato da API e o que seu time verá às 2 da manhã.

  • Visão do usuário: toda falha mostra uma mensagem simples mais um passo seguinte (tente de novo, faça login novamente, contate o suporte). Sem stack traces cru.
  • Visão da API: toda resposta de erro inclui um código estável e um correlation ID que também aparece nos logs do servidor.
  • Segurança dos logs: logs não contêm segredos (chaves de API), tokens, senhas, códigos one‑time ou dados completos de cartão de pagamento. Se precisar logar um identificador, masque‑o.
  • Comportamento de retry: falhas retryable são claramente marcadas e tratadas com cuidado (backoff curto, retries limitados e uma mensagem clara de “tente novamente”).
  • Workflow de suporte: alguém consegue diagnosticar o problema usando apenas o código de erro mais o correlation ID, sem pedir ao usuário para colar saída de devtools.

Um teste simples: dispare três falhas comuns (senha errada, sessão expirada, timeout do servidor). Se o usuário sabe o que fazer em seguida e seu time consegue encontrar a linha exata do log usando o correlation ID, você está pronto.

Um exemplo realista: transformar um crash de login bagunçado em um código claro

Adicionar uma boundary global de erro
Mapeamos erros lançados de forma desordenada em uma única forma de erro na qual seu app pode confiar.

Um problema comum em protótipos: um fluxo de login funciona na segunda e na terça uma atualização rápida faz com que todo mundo fique sem acesso. Alguns usuários veem “Algo deu errado”, outros veem uma página em branco, e seu time vê uma stack trace que menciona erro de banco de dados e uma biblioteca de auth ao mesmo tempo.

Com códigos de erro consistentes, você decide o que essa falha significa para usuários e suporte. Aqui, o app está falhando ao criar uma sessão depois de validar credenciais. Você a classifica como um problema de autenticação e mapeia para AUTH-003 (por exemplo: “Session creation failed”).

O que o usuário vê quando AUTH-003 acontece:

  • “Não conseguimos autenticar você. Tente novamente daqui a um minuto.”
  • “Se continuar acontecendo, contate o suporte e compartilhe este código: AUTH-003 e ID: 7F2K9.”

Essa mensagem é honesta, curta e segura. Não menciona tabelas, tokens, provedores ou outros internos.

Enquanto isso, seus logs mantêm os detalhes, mas apenas o que você pode armazenar com segurança: o código, o correlation ID, a rota da requisição, a versão do build e a causa raiz interna (por exemplo, “DB timeout ao gravar linha de sessão”). Campos sensíveis devem ser redigidos (email com hash, IP truncado, sem senhas, sem tokens completos).

Agora o suporte pode triagem rápido. Um usuário envia “AUTH-003, 7F2K9” e seu time puxa um único trilho de logs exato em vez de adivinhar.

Próximos passos para limpar uma base de código existente

Comece com evidência. Puxe suas últimas 1–2 semanas de logs e os tickets de suporte mais comuns, então liste as 20 mensagens de erro mais frequentes que usuários enfrentaram. Você geralmente verá repetições: timeouts, falhas de auth, registros faltando e crashes genéricos “Algo deu errado”.

Adicione estrutura em fatias pequenas. Escolha um endpoint de alto tráfego ou uma tela (login, checkout, upload de arquivo) e introduza códigos de erro consistentes ali primeiro. Quando isso estiver estável, expanda para a próxima fatia.

Um plano de rollout que funciona em times reais:

  • Agrupe os erros principais em 5 a 8 categorias (auth, validação, dependência, permissões, não encontrado, interno).
  • Adicione uma única camada de mapeamento que converta exceções lançadas em {code, userMessage, logContext}.
  • Mantenha um doc minúsculo para códigos: o que significam, quem os mantém e quando é permitido adicionar um novo.
  • Adicione testes para o mapeamento (um teste por código já é suficiente no início).
  • Revise logs para confirmar que você mantém contexto útil enquanto remove segredos e dados pessoais.

Se você herdou um codebase gerado por IA, espere throws imprevisíveis, formatos de erro misturados e problemas de segurança ocultos (como segredos em logs ou erros de banco vazando nas respostas). Nessa situação, estabilizar a boundary de erro e limpar o logging costuma dar o ganho mais rápido.

Se quiser uma segunda opinião sobre um app gerado por IA que está vazando erros ou produzindo logs bagunçados, FixMyMess (fixmymess.ai) foca em diagnosticar e reparar problemas como auth quebrada, logging arriscado e arquitetura espaguete. A auditoria de código gratuita deles pode ajudar a identificar os pontos de falha mais críticos antes de você se comprometer com mudanças.

Perguntas Frequentes

Why are thrown errors so inconsistent across the app?

Erros lançados tendem a variar por dispositivo, framework e caminho de código, então a mesma causa raiz pode aparecer como mensagens totalmente diferentes. Códigos de erro estáveis dão um rótulo compartilhado para suporte, análises e depuração, mesmo quando o texto visível ao usuário muda.

How many error code categories should I start with?

Comece com um conjunto pequeno que você consiga lembrar e aplicar, geralmente 5–8 grupos. Agrupe pelo que o usuário pode fazer a seguir (corrigir a entrada, fazer login novamente, tentar depois) em vez de por camadas internas como “repository” ou “service”.

What’s a good format for error codes that won’t break later?

Use um formato legível e estável como AUTH-001 ou DB-003 e mantenha o significado de cada código fixo depois que ele for lançado. Você pode alterar a redação da mensagem ao longo do tempo, mas não reutilize códigos para significados novos.

Should the user-facing error message be the same as the error code?

Não. O código é um identificador, não uma frase. Ele deve permanecer igual mesmo que você reescreva o texto da UI ou traduza o app. Coloque a explicação amigável ao humano no campo de mensagem e mantenha-a segura para os usuários.

What should an API error response include at minimum?

Mantenha o código estável, a mensagem clara e evite expor detalhes internos. Um padrão simples é retornar uma explicação curta, o que fazer em seguida e o código que o usuário pode compartilhar com o suporte.

Where should I map exceptions to codes and messages?

Faça o mapeamento de exceções internas para mensagens seguras ao usuário em um único lugar central, como um handler global de erros ou middleware. É aí que você converte, por exemplo, um timeout de banco de dados em um código consistente e numa mensagem simples de “tente novamente”, mantendo a stack trace apenas nos logs.

What should I log so debugging is fast but data stays safe?

Registre um código de erro e um correlation ID, depois adicione contexto suficiente para reproduzir o problema sem capturar segredos ou dados pessoais. Evite logar senhas, tokens, chaves de API e corpos completos de requisição, mesmo durante o debug.

How do I decide whether an error should be retryable?

Use o padrão do grupo: erros de validação geralmente não são retryable; falhas de dependência (banco ou serviços terceiros) frequentemente são. Se for fazer retry, seja gentil e limite o número de tentativas para não sobrecarregar o serviço em falha.

Why is it a problem to treat 404s and 500s the same?

Tratá‑los da mesma forma piora UX e operações, porque “não encontrado” é muitas vezes um resultado normal enquanto “erro interno” indica um problema de confiabilidade. Dê códigos e status diferentes para que a UI mostre o estado certo e seus alertas sejam significativos.

What’s the fastest way to clean this up in an AI-generated prototype?

Se você herdou um código gerado por IA, comece adicionando uma boundary global de erro e padronizando a forma de erro, depois limpe os logs para evitar vazamentos. Se quiser ajuda, FixMyMess (fixmymess.ai) pode executar uma auditoria gratuita de código e normalmente colocar protótipos quebrados em forma de produção em 48–72 horas.