Aikido

Come mantenere le funzioni concise: scrivere codice manutenibile

Leggibilità

Regola

Mantieni le funzioni concise.

Le funzioni  lunghesono difficili da comprendere, testare e manutenere.

Linguaggi supportati: 45+

Introduzione

Le funzioni che si estendono per centinaia di righe combinano più responsabilità, rendendo difficile capire cosa faccia la funzione senza leggere ogni singola riga. Le funzioni lunghe in genere gestiscono più aspetti come la validazione, la logica di business, la trasformazione dei dati e la gestione degli errori tutti nello stesso posto. Questo viola il principio di responsabilità singola e crea codice difficile da testare, debuggare e modificare senza compromettere il comportamento esistente.

Perché è importante

Manutenibilità del codice: Le funzioni lunghe richiedono agli sviluppatori di mantenere un contesto più ampio in memoria per comprendere il comportamento del codice. Modificare una parte rischia di romperne un'altra perché tutta la logica è interconnessa. La correzione dei bug diventa rischiosa poiché gli effetti collaterali indesiderati sono difficili da prevedere.

Complessità dei test: Testare una funzione di 200 righe significa coprire tutti i possibili percorsi del codice in un unico test, richiedendo una configurazione complessa e numerosi casi di test. Funzioni più piccole possono essere testate in modo indipendente con unit test mirati, rendendo le suite di test più veloci e affidabili.

Esempi di codice

❌ Non conforme:

async function processOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
    const subtotal = orderData.items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    const tax = subtotal * 0.08;
    const total = subtotal + tax + (subtotal > 50 ? 0 : 9.99);
    const order = await db.orders.create({
        customerId: orderData.customer.id,
        total: total
    });
    await emailService.send(orderData.customer.email, `Order #${order.id}`);
    await inventory.reserve(orderData.items);
    return order;
}

Perché è errato: Questa funzione gestisce validazione, calcolo, operazioni di database, invio di email e inventario. Il testing richiede il mocking di tutte le dipendenze. Qualsiasi modifica alla logica fiscale o di validazione richiede la modifica dell'intera funzione.

✅ Conforme:

function validateOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
}

function calculateTotal(items) {
    const subtotal = items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    return subtotal + (subtotal * 0.08) + (subtotal > 50 ? 0 : 9.99);
}

async function createOrder(customerId, total) {
    return await db.orders.create({ customerId, total });
}

async function processOrder(orderData) {
    validateOrder(orderData);
    const total = calculateTotal(orderData.items);
    const order = await createOrder(orderData.customer.id, total);
    
    // Non-critical operations in background
    emailService.send(orderData.customer.email, `Order #${order.id}`).catch(console.error);
    
    return order;
}

Perché questo è importante: Ogni funzione ha una sola e chiara responsabilità. validateOrder() e calculateTotal() possono essere testati in modo indipendente senza mock. createOrder() isola la logica del database. Le operazioni relative alle email e all'inventario non bloccano la creazione dell'ordine e gli eventuali errori vengono gestiti separatamente.

Conclusione

Evolvi le API attraverso modifiche additive: aggiungi nuovi campi, nuovi endpoint o parametri opzionali. Quando le modifiche che rompono la compatibilità sono inevitabili, utilizza il versionamento delle API per eseguire la vecchia e la nuova versione in parallelo. Depreca i vecchi campi fornendo tempistiche chiare e guide di migrazione prima di rimuoverli.

FAQ

Hai domande?

Come faccio a suddividere funzioni troppo lunghe?

Identifica le singole responsabilità all'interno della funzione. Estrai la logica di validazione in funzioni separate. Isola i calcoli in funzioni pure. Sposta le operazioni di I/O (database, chiamate API) in funzioni dedicate. Ogni funzione estratta dovrebbe avere uno scopo unico e ben definito, con un nome descrittivo.

Le funzioni piccole non introducono overhead e danneggiano le performance?

I compilatori e gli interpreti moderni eseguono l'inlining delle funzioni piccole, azzerando l'overhead di chiamata. L'impatto sulle performance è trascurabile rispetto ai vantaggi in termini di manutenibilità. Fai sempre del profiling prima di ottimizzare. Un codice leggibile è molto più facile da ottimizzare in un secondo momento, quando si individuano i veri colli di bottiglia.

Cosa fare con le funzioni che contengono molti passaggi sequenziali?

I passaggi sequenziali indicano un flusso di lavoro che può essere suddiviso in funzioni più piccole. Crea funzioni di supporto per ogni singolo passaggio e chiamale in sequenza da una funzione coordinatrice. In questo modo il flusso di lavoro risulta leggibile e ogni singolo passaggio può essere testato in modo indipendente.

‍

Come gestire le funzioni che richiedono molti parametri dopo l'estrazione?

Passa oggetti contenenti parametri correlati anziché liste di argomenti troppo lunghe. In alternativa, valuta se le funzioni estratte dovrebbero essere metodi di una classe che gestisce lo stato condiviso. Se una funzione richiede 6 o più parametri, potrebbe essere il sintomo di una scarsa astrazione o di strutture dati mancanti.

Dovrei estrarre le funzioni anche se vengono chiamate una sola volta?

Sì, se l'estrazione migliora la leggibilità. Una funzione estratta con un nome appropriato spiega cosa fa un blocco di codice molto meglio dei commenti. L'estrazione di funzioni monouso è utile quando serve a chiarire una logica complessa o a ridurre i livelli di annidamento nella funzione principale.

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.