Aikido

Perché dovresti gestire gli errori nei blocchi catch anziché lasciarli vuoti

Leggibilità

Regola
Gestisci gli errori nei blocchi catch. 
I blocchi catch vuoti ignorano silenziosamente gli errori, 
rendendo il debugging difficile. 
Linguaggi supportati: Java, C, C++, PHP, JavaScript, 
TypeScript, Go, Python

Introduzione

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

Perché è importante

Debugging e incident response: I blocchi catch vuoti eliminano i log degli errori. Gli ingegneri non hanno a disposizione alcuno stack trace, messaggio di errore o indicazione su quando o dove si sia verificato il guasto, rendendo i problemi quasi impossibili da riprodurre.

Corruzione silenziosa dei dati: Quando le operazioni sul database o le chiamate API falliscono all'interno di blocchi catch vuoti, l'applicazione continua come se avessero avuto successo. I record vengono aggiornati solo parzialmente, le transazioni sono incomplete e, nel momento in cui la corruzione viene scoperta, la traccia di audit è svanita.

Vulnerabilità di sicurezza: I blocchi catch vuoti mascherano problemi di sicurezza come errori di autenticazione o controlli di autorizzazione. Un attaccante che innesca un'eccezione in un percorso critico per la sicurezza potrebbe aggirare completamente le protezioni se l'errore viene ignorato silenziosamente.

Guasti a cascata: Quando gli errori vengono ignorati, l'applicazione continua in uno stato non valido. Le operazioni successive che dipendono dal risultato dell'operazione fallita falliranno a loro volta, creando una catena di guasti che devia l'attenzione degli ingegneri 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é è errato: Se un'operazione qualsiasi fallisce, l'errore viene silenziosamente ignorato e la funzione restituisce un esito positivo. Il database potrebbe essere aggiornato, ma l'invalidazione della cache potrebbe fallire, lasciando dati obsoleti. Oppure l'aggiornamento dell'indice di ricerca fallisce, rendendo l'utente non rintracciabile, senza alcun log o avviso che segnali 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 con il relativo contesto, fornendo informazioni utili per il debugging. L'errore si propaga al chiamante, consentendo una corretta gestione dell'errore al livello appropriato. I sistemi di monitoraggio possono generare avvisi su questi errori e l'applicazione fallisce tempestivamente anziché continuare con uno stato non valido.

Conclusione

I blocchi catch vuoti non sono mai accettabili nel codice in produzione. Ogni eccezione intercettata richiede come minimo la registrazione nei log, e la maggior parte deve propagarsi ai chiamanti o attivare azioni di recupero specifiche. Se è veramente necessario ignorare un errore, documentane il motivo con un commento che spieghi la giustificazione di business. L'approccio predefinito dovrebbe essere sempre quello di gestire gli errori in modo esplicito, senza scartarli silenziosamente.

FAQ

Hai domande?

Cosa succede se ho davvero bisogno di ignorare determinati errori?

Documentalo in modo esplicito con un commento che spieghi perché è sicuro ignorare l'errore. Registra l'errore a livello di debug in modo che compaia nei log dettagliati senza tuttavia attivare avvisi. Valuta se ignorare l'errore possa portare a uno stato non valido. Anche per errori "attesi" come cache miss o timeout di rete, la registrazione nei log aiuta i team di operations a comprendere i pattern di comportamento del sistema.

Dovrei registrare sempre gli errori nei blocchi catch?

La registrazione nei log è solitamente una buona pratica, poiché non è possibile fare il debugging dei problemi senza vedere cosa è fallito. Ci sono casi in cui è possibile risalire al problema senza log, ad esempio rilanciando immediatamente l'errore affinché venga gestito altrove, oppure se l'applicazione deve arrestarsi e riavviarsi in caso di guasti critici. Tuttavia, una corretta registrazione aiuta sempre.

Qual è la differenza tra registrare nei log e rilanciare gli errori?

La registrazione nei log memorizza ciò che è accaduto a fini di debugging e monitoraggio. Il rilancio (re-throwing) propaga l'errore ai chiamanti affinché possano decidere come rispondere. Fai entrambe le cose: registra l'errore con il contesto nel punto in cui si verifica il fallimento, quindi rilancialo (eventualmente racchiuso in un tipo di errore più specifico) per consentire ai chiamanti di gestirne il recupero. Non registrare lo stesso errore a più livelli, poiché genera rumore.

Come gestisco gli errori che si verificano nei blocchi finally?

I blocchi finally dovrebbero raramente generare errori. Se devono eseguire operazioni inclini a errori (come la chiusura di risorse), racchiudile in un blocco try-catch dedicato. Registra eventuali errori ma evita che mascherino l'errore originale. Alcuni linguaggi offrono una sintassi per gestire sia l'errore principale sia quelli del blocco finally; sfrutta questi meccanismi per preservare entrambi i contesti di errore.

Che dire dell'impatto sulle prestazioni della registrazione di ogni errore?

La scrittura di log ha un costo irrisorio rispetto a quello di effettuare il debugging di problemi in produzione senza log. I moderni framework di logging sono altamente ottimizzati. Se si verificano così tanti errori da compromettere le prestazioni, è opportuno correggere gli errori anziché nasconderli. Tassi di errore elevati indicano problemi gravi che i blocchi catch vuoti non fanno altro che peggiorare.

I blocchi catch dovrebbero sempre generare errori (throw) o possono restituire valori di errore?

Dipende dal linguaggio e dall'architettura. In JavaScript con le Promise, un'eccezione lanciata dal blocco catch si propaga al gestore di errori successivo. Restituire un oggetto di errore da un blocco catch risolve la promise con quell'errore, il che di solito è scorretto. Familiarizza con la semantica di gestione degli errori del tuo linguaggio. In linea generale, lascia che gli errori si propaghino a meno che tu non possa gestirne il recupero in modo significativo.

Come gestisco gli errori nelle operazioni asincrone che non dispongono di un blocco try-catch?

Utilizza gestori .catch() sulle promesse, event listener di errore sugli event emitter o callback di errore nelle API basate su callback. Non ignorare mai i gestori dei rifiuti (rejections) o le callback di errore. I rifiuti di promesse non gestiti dovrebbero essere monitorati a livello di processo e trattati come guasti critici. I moderni ambienti Node.js possono terminare l'esecuzione in presenza 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.