Aikido

Perché è meglio evitare l'assegnazione di valori nelle istruzioni condizionali per prevenire bug nascosti

Leggibilità

Regola
Non assegnare i compiti all'interno espressioni condizionali. 
Mescolare assegnazione e logica logica rende il codice soggetto a errori
e più difficile da comprendere. Assegnare compiti da controlli . 

Linguaggi :** JavaScript, TypeScript, Python, PHP

Introduzione

Gli operatori di assegnazione all’interno delle istruzioni condizionali sono una fonte comune di bug che spesso sfuggono ai compilatori e ai linter. L'errore classico consiste nell'utilizzare = (assegnazione) invece di == o === (confronto) in un'istruzione if, ma il problema è più profondo. Anche le assegnazioni intenzionali all'interno delle istruzioni condizionali rendono il codice difficile da leggere, revisionare e debuggare. Quando l'assegnazione e la valutazione avvengono sulla stessa riga, chi legge deve analizzare mentalmente quale operazione abbia la precedenza e quale valore venga effettivamente verificato.

Perché è importante

Perché è importante

Descrizione del bug: Correzione di un errore di battitura === a = non causerà un errore di sintassi, ma modificherà semplicemente il comportamento senza dare alcun avviso. La condizione restituisce il valore assegnato (vero/falso), non il risultato del confronto.

Leggibilità del codice: i lettori si aspettano che le istruzioni condizionali verifichino i valori, non che li modifichino. Quando entrambe le operazioni avvengono contemporaneamente, i responsabili della manutenzione devono individuare quali variabili vengono modificate e in quale momento.

Esempi di codice

❌ Non conforme:

function processUser(userData) {
    if (user = userData.user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    if (result = value.match(/^\d{3}-\d{2}-\d{4}$/)) {
        return result[0];
    }
    return false;
}

Perché è sbagliato: le assegnazioni all'interno delle istruzioni condizionali rendono difficile capire se si tratti di una scelta intenzionale o di un errore di battitura. Il primo esempio potrebbe essere un bug in cui si intendeva usare ===, mentre il secondo mescola la corrispondenza con espressioni regolari e l'assegnazione, rendendo difficile seguire il flusso del codice.

✅ Conforme:

function processUser(userData) {
    const user = userData.user;
    if (user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    const result = value.match(/^\d{3}-\d{2}-\d{4}$/);
    if (result) {
        return result[0];
    }
    return false;
}

Perché è importante: Separare l'assegnazione dalla condizione rende l'intento chiarissimo. I lettori capiscono immediatamente che utente viene prima estratto, poi verificato. Il risultato della corrispondenza dell'espressione regolare viene catturato, quindi valutato. Nessuna ambiguità, nessun sovraccarico cognitivo e errori di battitura come = vs === diventa evidente.

Conclusione

Tenere separate le assegnazioni dalle condizioni è una regola semplice che permette di evitare un'intera categoria di bug. Lo sforzo cognitivo richiesto dall'analisi di operazioni combinate supera di gran lunga qualsiasi presunto vantaggio in termini di concisione. Un codice chiaro ed esplicito, in cui l'assegnazione e la valutazione sono operazioni distinte, migliora la leggibilità, riduce i bug e rende più efficace la revisione del codice.

Domande frequenti

Hai delle domande?

E nei casi in cui l'assegnazione nelle espressioni condizionali è un'espressione idiomatica, come nella lettura di un file?

Anche nei linguaggi in cui è comune l'uso di `while (line = file.readline())`, le migliori pratiche moderne privilegiano una separazione esplicita. In JavaScript, si utilizzano i protocolli iteratori: `for (const line of fileLines)`. In Python 3.8 e versioni successive, l'operatore walrus `:=` rende esplicito l'intento quando l'assegnazione nelle condizioni è realmente necessaria, ma anche in tal caso è opportuno valutare se l'uso di istruzioni separate non risulti più chiaro.

La separazione tra l'assegnazione e le istruzioni condizionali comporta delle implicazioni in termini di prestazioni?

No. I moderni motori JavaScript ottimizzano entrambi gli schemi allo stesso modo. La separazione comporta l'aggiunta di una dichiarazione di variabile, che dopo la compilazione non comporta alcun costo in termini di tempo di esecuzione. Qualsiasi differenza di prestazioni percepita è trascurabile rispetto ai vantaggi in termini di prevenzione dei bug e leggibilità. Scrivete innanzitutto codice chiaro e ottimizzate solo quando l'analisi delle prestazioni individua effettivi colli di bottiglia.

Come si gestiscono espressioni del tipo if ((match = regex.exec(str)) !== null)?

Suddividilo in due istruzioni: const match = regex.exec(str); if (match !== null). Oppure, meglio ancora, usa alternative più moderne: const match = str.match(regex); if (match). Il controllo esplicito del valore null diventa superfluo perché match() restituisce null in caso di errore, il che è considerato falso. La chiarezza migliora e l'intento diventa evidente.

E che dire delle assegnazioni utilizzate intenzionalmente per il loro valore di ritorno?

Il fatto che sia intenzionale non significa che sia una buona pratica. Il codice che si basa sui valori di ritorno delle assegnazioni nelle istruzioni condizionali crea rischi per la manutenzione. Chi modificherà il codice in futuro potrebbe “correggere” quello che sembra un errore di battitura. Se devi assolutamente utilizzare questo modello, aggiungi un commento che ne spieghi il motivo, ma valuta se sia possibile ristrutturare il codice in modo più chiaro.

Questa regola si applica agli operatori ternari?

Sì. Evita di scrivere `const x = (y = getValue()) ? y : defaultValue`. È ancora più difficile da leggere rispetto alle istruzioni `if`. Usa invece: `const y = getValue(); const x = y ? y : defaultValue`. Oppure, meglio ancora, usa il coalescing nullish: `const x = getValue() ?? defaultValue`. Gli operatori moderni sono stati creati proprio per evitare questi schemi poco intuitivi.

In che modo i linter e gli strumenti di analisi statica gestiscono questo modello?

La maggior parte dei linter moderni segnala le assegnazioni nelle istruzioni condizionali per impostazione predefinita o tramite configurazione. In genere richiedono l’uso di parentesi aggiuntive (if ((x = y))) per indicare che si tratta di un’assegnazione intenzionale, ma questa pratica è considerata un “code smell”. È preferibile disabilitare l’eccezione del linter e correggere il codice in modo corretto. Gli strumenti di analisi statica sono in grado di rilevare questi modelli durante il processo di CI/CD, impedendo che raggiungano l’ambiente di produzione.

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.