Aikido

Tratamento de erros em blocos de captura: porque é que blocos de captura vazios quebram os sistemas de produção

Legibilidade

Regra
Manusear erros em catch blocos. 
Vazio catch vazios silenciosamente engolir erros, 
tornando a depuração difícil. 
Linguagens suportadas: Java, C, C++, PHP, JavaScript, 
TypeScript, Go, Python

Introdução

Os blocos de captura vazios são um dos antipadrões mais perigosos no código de produção. Quando as excepções são capturadas mas não tratadas, o erro desaparece sem deixar rasto. A aplicação continua a funcionar com o estado corrompido, dados inválidos ou operações falhadas que deveriam ter parado a execução. Os utilizadores vêem falhas silenciosas em que as funcionalidades não funcionam, mas não recebem mensagens de erro. As equipas de operações não têm registos para depurar. A única indicação de que algo está errado surge horas ou dias depois, quando as falhas em cascata tornam o sistema inutilizável.

Porque é importante

Depuração e resposta a incidentes: Os blocos de captura vazios eliminam os registos de erros. Os engenheiros não têm rastreamento de pilha, mensagem de erro ou indicação de quando ou onde a falha ocorreu, tornando os problemas quase impossíveis de reproduzir.

Corrupção silenciosa de dados: Quando as operações de base de dados ou chamadas de API falham dentro de blocos de captura vazios, a aplicação continua como se tivessem sido bem sucedidas. Os registos são parcialmente actualizados, as transacções ficam incompletas e, quando a corrupção é descoberta, a pista de auditoria já desapareceu.

Vulnerabilidades de segurança: Os blocos de captura vazios mascaram as falhas de segurança, como erros de autenticação ou verificações de autorização. Um atacante que desencadeie uma exceção num caminho crítico para a segurança pode contornar completamente as protecções se o erro for silenciosamente engolido.

Falhas em cascata: Quando os erros são engolidos, a aplicação continua num estado inválido. As operações subsequentes que dependem do resultado da operação falhada também falharão, criando uma cadeia de falhas que desvia os engenheiros da verdadeira causa principal.

Exemplos de código

Não conforme:

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
    } catch (error) {
        // TODO: handle error
    }

    return { success: true };
}

Porque é que está errado: Se alguma operação falhar, o erro é silenciosamente ignorado e a função devolve o sucesso. A base de dados pode ser actualizada, mas a invalidação da cache pode falhar, deixando os dados obsoletos. Ou a atualização do índice de pesquisa falha, tornando o utilizador indecifrável, sem qualquer registo ou alerta que indique o problema.

Conformidade:

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
        return { success: true };
    } catch (error) {
        logger.error('Failed to update user profile', {
            userId,
            error: error.message,
            stack: error.stack
        });
        throw new ProfileUpdateError(
            'Unable to update profile',
            { cause: error }
        );
    }
}

Porque é que isto é importante: Todos os erros são registados com contexto, fornecendo informações de depuração. O erro se propaga para o chamador, permitindo o tratamento adequado de erros no nível apropriado. Os sistemas de monitorização podem alertar sobre estes erros e a aplicação falha rapidamente em vez de continuar com um estado inválido.

Conclusão

Blocos catch vazios nunca são aceitáveis em código de produção. Todas as excepções capturadas precisam de ser registadas, no mínimo, e a maioria precisa de se propagar a quem as chama ou de desencadear acções de recuperação específicas. Se for realmente necessário ignorar um erro, documente o motivo com um comentário que explique a justificação comercial. O padrão deve ser sempre tratar os erros explicitamente, e não descartá-los silenciosamente.

FAQs

Tem perguntas?

E se eu precisar mesmo de ignorar certos erros?

Documentá-lo explicitamente com um comentário que explique porque é que o erro pode ser ignorado. Registe o erro no nível de depuração para que apareça nos registos detalhados, mas não desencadeie alertas. Considere se ignorar o erro pode levar a um estado inválido. Mesmo para erros "esperados", como falhas de cache ou timeouts de rede, o registo ajuda as equipas de operações a compreender os padrões de comportamento do sistema.

Devo registar sempre os erros nos blocos de captura?

O registo é normalmente uma boa ideia porque não é possível depurar problemas sem ver o que falhou. Há casos em que é possível rastrear o problema sem os registos, como o lançamento imediato de um novo erro para ser tratado noutro local, ou se a aplicação deve falhar e reiniciar em caso de falhas críticas. Mas o registo adequado ajuda sempre.

Qual é a diferença entre registar e voltar a lançar erros?

O registo regista o que aconteceu para depuração e monitorização. O relançamento propaga o erro aos chamadores para que eles possam decidir como responder. Faça ambos: registe o erro com contexto no ponto de falha e, em seguida, volte a lançar (possivelmente envolto num tipo de erro mais específico) para permitir que os utilizadores tratem da recuperação. Não registe o mesmo erro em vários níveis, pois isso cria ruído.

Como é que trato os erros que ocorrem nos blocos finally?

Os blocos finais raramente devem lançar erros. Se tiverem de efetuar operações propensas a erros (como fechar recursos), envolva-as no seu próprio try-catch. Registe todos os erros, mas não os deixe mascarar o erro original. Algumas linguagens fornecem sintaxe para tratar tanto o erro principal quanto os erros do bloco final. Use esses mecanismos para preservar ambos os contextos de erro.

E quanto ao impacto no desempenho do registo de todos os erros?

O registo é barato comparado com o custo de depuração de problemas de produção sem registos. As estruturas de registo modernas são altamente optimizadas. Se você tiver tantos erros que o registro em log afete o desempenho, corrija os erros em vez de ocultá-los. Altas taxas de erro indicam problemas sérios que blocos de captura vazios só piorarão.

Os blocos catch devem sempre lançar erros ou podem devolver valores de erro?

Depende da linguagem e da arquitetura. Em JavaScript com promessas, o lançamento do catch propaga-se para o próximo manipulador de erros. Retornar um objeto de erro do catch resolve a promessa com esse erro, o que geralmente é errado. Familiarize-se com a semântica de tratamento de erros da sua linguagem. Em geral, deixe os erros se propagarem, a menos que você possa recuperá-los de forma significativa.

Como é que trato os erros em operações assíncronas que não têm try-catch?

Use manipuladores .catch() em promessas, ouvintes de eventos de erro em emissores de eventos ou retornos de chamada de erro em APIs baseadas em retorno de chamada. Nunca ignore manipuladores de rejeição ou retornos de chamada de erro. As rejeições de promessas não tratadas devem ser monitoradas no nível do processo e tratadas como falhas críticas. O Node.js moderno pode terminar em rejeições não tratadas, o que é melhor do que uma falha silenciosa.

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.