Aikido

Perché è meglio gestire gli errori nei blocchi `catch` invece di lasciarli vuoti

Leggibilità

Regola
Gestire errori in catch . 
Blocco catch blocchi assorbono silenziosamente ignorano gli errori, 
rendendo il debug difficile. 
Linguaggi supportati: Java, C, C++, PHP, JavaScript, 
TypeScript, Go, Python

Introduzione

I blocchi di intercettazione vuoti sono uno degli anti-pattern più pericolosi nel codice di produzione. Quando le eccezioni vengono intercettate ma non gestite, l’errore scompare senza lasciare traccia. L’applicazione continua a funzionare con uno stato corrotto, dati non validi o operazioni fallite che avrebbero dovuto interrompere l’esecuzione. Gli utenti riscontrano errori silenziosi in cui le funzionalità non funzionano, ma non ricevono alcun messaggio di errore. I team operativi non dispongono di log su cui basare il debug. L’unico indizio che qualcosa non va arriva ore o giorni dopo, quando i guasti a cascata rendono il sistema inutilizzabile.

Perché è importante

Debug e risposta agli incidenti: i blocchi `catch` vuoti eliminano i log degli errori. Gli ingegneri non dispongono di trace dello stack, messaggi di errore o indicazioni su quando o dove si sia verificato il guasto, rendendo quasi impossibile riprodurre i problemi.

Corruzione silenziosa dei dati: quando le operazioni sul database o le chiamate alle API falliscono all’interno di blocchi catch vuoti, l’applicazione prosegue come se avessero avuto esito positivo. I record vengono aggiornati solo parzialmente, le transazioni rimangono incomplete e, quando la corruzione viene individuata, la traccia di audit è ormai andata perduta.

Vulnerabilità di sicurezza: i blocchi catch vuoti nascondono problemi di sicurezza quali errori di autenticazione o controlli di autorizzazione. Un malintenzionato che provochi un'eccezione in un percorso critico per la sicurezza potrebbe aggirare completamente le protezioni se l'errore venisse ignorato senza alcun avviso.

Errori a cascata: quando gli errori vengono ignorati, l'applicazione continua a funzionare in uno stato non valido. Anche le operazioni successive, che dipendono dal risultato dell'operazione fallita, falliranno a loro volta, creando una catena di errori che distoglie i tecnici dalla vera causa principale.

Esempi di codice

❌ Non 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 };
}

Perché è sbagliato: se un'operazione fallisce, l'errore viene ignorato senza alcun avviso e la funzione restituisce un esito positivo. Il database potrebbe essere aggiornato, ma l'invalidazione della cache potrebbe non andare a buon fine, lasciando dati non aggiornati. Oppure l'aggiornamento dell'indice di ricerca potrebbe fallire, rendendo l'utente non ricercabile, senza che alcun log o avviso indichi il problema.

✅ Conforme:

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 }
        );
    }
}

Perché è importante: ogni errore viene registrato insieme al contesto, fornendo informazioni utili per il debug. L'errore viene propagato al chiamante, consentendo una corretta gestione dell'errore al livello appropriato. I sistemi di monitoraggio possono segnalare questi errori e l'applicazione si arresta rapidamente anziché continuare a funzionare in uno stato non valido.

Conclusione

I blocchi di intercettazione vuoti non sono mai accettabili nel codice di produzione. Ogni eccezione intercettata deve essere quantomeno registrata nei log, e nella maggior parte dei casi deve essere propagata ai chiamanti o attivare specifiche azioni di ripristino. Se è davvero necessario ignorare un errore, documentarne il motivo con un commento che ne spieghi la giustificazione aziendale. L'impostazione predefinita dovrebbe sempre essere quella di gestire gli errori in modo esplicito, non di ignorarli silenziosamente.

Domande frequenti

Hai delle domande?

E se avessi davvero bisogno di ignorare determinati errori?

Documentarlo esplicitamente con un commento che spieghi perché è sicuro ignorare l'errore. Registrare l'errore a livello di debug in modo che compaia nei log dettagliati ma non attivi avvisi. Valutare se ignorare l'errore possa portare a uno stato non valido. Anche per errori "previsti" come mancati accessi alla cache o timeout di rete, la registrazione aiuta i team operativi a comprendere i modelli di comportamento del sistema.

Devo sempre registrare gli errori nei blocchi catch?

La registrazione dei log è solitamente una buona idea, perché non è possibile risolvere i problemi senza capire cosa sia andato storto. Ci sono casi in cui è possibile individuare il problema anche senza log, ad esempio quando l'errore viene immediatamente rigenerato per essere gestito altrove, oppure se l'applicazione va in crash e si riavvia in caso di errori critici. Tuttavia, una corretta registrazione dei log è sempre utile.

Qual è la differenza tra la registrazione degli errori e il loro re-throwing?

La registrazione in log documenta ciò che è accaduto a fini di debug e monitoraggio. Il re-throw propaga l'errore ai chiamanti, in modo che possano decidere come reagire. È consigliabile fare entrambe le cose: registrare l'errore con il contesto nel punto in cui si è verificato il guasto, quindi re-throw (eventualmente avvolgendo l'errore in un tipo di errore più specifico) per consentire ai chiamanti di gestire il ripristino. Non registrare lo stesso errore a più livelli, poiché ciò crea rumore.

Come posso gestire gli errori che si verificano nei blocchi `finally`?

I blocchi `finally` dovrebbero generare errori solo in casi eccezionali. Se devono eseguire operazioni soggette a errori (come la chiusura delle risorse), è opportuno racchiuderle in un proprio blocco `try-catch`. Registrare eventuali errori, ma senza lasciare che questi nascondano l'errore originale. Alcuni linguaggi offrono una sintassi per gestire sia l'errore principale che gli errori dei blocchi `finally`: utilizzare questi meccanismi per preservare entrambi i contesti di errore.

E che dire dell'impatto sulle prestazioni derivante dalla registrazione di ogni errore?

La registrazione degli errori è poco costosa rispetto al costo del debug dei problemi in produzione in assenza di log. I moderni framework di registrazione sono altamente ottimizzati. Se gli errori sono così numerosi da compromettere le prestazioni, è meglio correggerli piuttosto che nasconderli. Tassi di errore elevati indicano problemi gravi che i blocchi `catch` vuoti non faranno altro che aggravare.

I blocchi "catch" dovrebbero sempre generare errori, oppure possono restituire valori di errore?

Dipende dal linguaggio e dall'architettura. In JavaScript, con le promesse, il lancio di un errore da un blocco `catch` si propaga al gestore di errori successivo. Restituire un oggetto di errore da un blocco `catch` risolve la promessa con quell'errore, il che di solito è sbagliato. È importante acquisire familiarità con la semantica di gestione degli errori del proprio linguaggio. In generale, è consigliabile lasciare che gli errori si propaghino, a meno che non sia possibile effettuare un recupero significativo.

Come posso gestire gli errori nelle operazioni asincrone che non prevedono l'uso di try-catch?

Utilizza i gestori .catch() sulle promesse, i listener di eventi di errore sugli emettitori di eventi o i callback di errore nelle API basate su callback. Non ignorare mai i gestori di rifiuto o i callback di errore. I rifiuti delle promesse non gestiti devono essere monitorati a livello di processo e trattati come errori critici. Le versioni moderne di Node.js possono terminare l'esecuzione in caso di rifiuti non gestiti, il che è preferibile a un errore silenzioso.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.