Tieni basso il livello di indentazione [duplicato]

9

Ho sentito molto che non dovresti scrivere funzioni più grandi di una dimensione dello schermo, che dovresti estrarre le cose in funzioni se chiami le cose molto spesso e tutte queste linee guida sulla codifica. Uno di questi è anche mantenere basso il livello di indentazione. Alcune persone mi hanno detto che avrei dovuto indentarlo al massimo 4 volte.

Ma in tutti i giorni la programmazione continua a succedere che ho bisogno di O (n ^ 2) o O (n ^ 3) cicli con un controllo di flusso, che crea dal nulla 7 o 8 livelli di indentazione ...

Ci sono dei buoni modi per evitarlo? Un problema è che, se si tenta di evitare l'annidamento complesso di istruzioni if / else, io tendo a farlo in una riga che può sembrare molto terribile ed essere illeggibile e non molto facile da mantenere.

    
posta reox 06.12.2013 - 12:17
fonte

5 risposte

15

Come hai già notato, il raggruppamento della logica di controllo per ridurre il rientro è totalmente controproducente. Il problema non è in realtà con livelli di indentazione o numero di linee. Queste cose sono solo indicatori del problema reale: fare troppo in un pezzo di codice.

La soluzione consiste nel rifattorizzare cicli complicati nidificati in metodi separati in modo che sia il metodo chiamante che quello chiamato possano essere interpretati come se facessero una cosa sensata. Se i loop hanno bisogno di troppe variabili locali per essere facilmente separabili, un oggetto metodo può essere utile che incapsula quelle variabili con un ampio scope locale nello scope di classe di una nuova mini-classe speciale.

Lo stesso vale per la logica complicata del controllore: se la condizione di continuare o arrestare un ciclo contiene molte espressioni complicate, quindi rifattarla in un metodo booleano con un nome ragionevole. L'obiettivo è di rendere la logica di primo livello ovviamente corretta purché le funzioni ausiliarie siano corrette e l'helper funzioni così piccole da poter essere facilmente verificate da sole.

    
risposta data 06.12.2013 - 12:25
fonte
12

Invertito se

La maggior parte degli sviluppatori pensa in termini di logica nidificata. È difficile per loro invertire la loro logica per ridurre il nidificazione. Il codice profondamente annidato è più difficile da leggere e per ridurre il nesting è necessario invertire la logica.

Quando un programmatore scrive il codice, scrive in una struttura ad albero logica come questa.

 bool function publish(Document doc)
 {
    if(doc != null)
    {
        if(doc.status == "finished")
        {
            doc.print();
            if(doc.locked)
            {
                doc.unlock();
            }

            return true;
        }

        return false;
    }

    return false;
 }

Quanto sopra è logicamente corretto, ma introduce i rientri che lo rendono difficile da mantenere in futuro. È difficile, perché la logica aggiuntiva richiederà blocchi più profondi. Più è approfondito, più è probabile che venga introdotto un errore.

Invertendo le tue istruzioni if e combinandole puoi ridurre il nidificazione.

 bool function publish(Document doc)
 {
    if(doc == null || doc.status != "finished")
    {
        return false
    }

    doc.print();

    if(!doc.locked)
    {
        return true;
    }

    doc.unlock();

    return true;
 }

Questo tipo di logica invertita mantiene tutte le operazioni della funzione all'interno del primo blocco rientrato e le istruzioni if vengono utilizzate solo per terminare la funzione.

Dividi loop e lavoro

Non eseguire le tue operazioni di lavoro all'interno di un blocco di loop. Separare l'iterazione dall'attività stessa. Questo crea funzioni a singolo scopo.

Prendi questo esempio

function void PrintDocuments(List<Document> pDocuments)
{
    for(int doc=0; doc < pDocuments.Count; doc++)
    {
        for(int page=0; page < pDocuments[doc].Pages.Count; page++)
        {
            pDocuments[doc].Pages[page].Print();
        }
    }
}

Anche se non è molto complesso, crea 3 livelli di nidificazione e la funzione sta eseguendo tre diversi compiti.

Dividi il lavoro e i cicli lo rendono più facile da mantenere.

function void PrintDocuments(List<Document> pDocuments)
{
    for(int doc=0; doc < pDocuments.Count; doc++)
    {
        PrintPages(pDocuments[doc].Pages);
    }
}

private function void PrintPages(Document pDocument)
{
    for(int page=0; page < pDocument.Pages.Count; page++)
    {
        PrintPage(Pages[page]);
    }
}

private function void PrintPage(Page pPage)
{
    pPage.Print();
}

Si potrebbe obiettare che quanto sopra è un lavoro extra, ma il tempo speso per dividere le attività e creare funzioni a singolo scopo ridurrà gli sforzi in seguito quando si manterrà il codice. L'indentazione ridotta facilita la lettura. Il lavoro che deve essere completato da un ciclo non viene mai eseguito all'interno di un ciclo, ma viene gestito da una funzione dedicata per quel lavoro PrintPage .

Conclusione

Scopo singolo e logica invertita sono le chiavi per ridurre i rientri.

    
risposta data 06.12.2013 - 17:52
fonte
6

L'idea alla base di queste linee guida è che, quando ti trovi in una situazione come la tua, è molto probabile (non sempre, sia chiaro, ma ancora più spesso di quanto pensi) qualcosa di sbagliato nell'algoritmo, e tu dovrebbe fare un passo indietro e ripensarlo.

In realtà non penso che il refactoring ti aiuterà molto, se è davvero un algoritmo O (n²) o O (n³) - potrebbe persino peggiorare la leggibilità (per le persone con terminali sufficientemente ampi ...) e ridurre la velocità. (Certo, se puoi migliorare la situazione rifattendo senza perdendo qualcosa, fallo assolutamente!) Davvero, fai un passo indietro, forse lascia che un collega o qualcun altro guardino al codice e all'algoritmo e dare consigli ... e se non altro aiuta, ignora le linee guida per quella funzione - con un commento dettagliato in alto, ovviamente.

    
risposta data 06.12.2013 - 12:27
fonte
2
  • Posiziona gli anelli più interni in funzioni. Questo aiuta anche la leggibilità.
  • Riorganizza il tuo codice in cortocircuito invece di nidificare. Ad esempio, anziché if (condition) {big long nested code} , utilizza if (!condition) return;
  • Pre-elaborare i dati in un modulo intermedio che non richiede tanto nidificazione. Ad esempio, se copi un elenco collegato in un array all'inizio, potresti evitare un ciclo nidificato in seguito che attraversa l'elenco collegato.
  • Utilizza la ricorsione anziché i cicli nidificati. La ricorsione spesso semplifica enormemente il codice profondamente annidato.
  • Utilizza le eccezioni per uscire dall'annidamento durante le condizioni di errore invece di controllare ad ogni livello.
  • Cerca di trovare algoritmi più efficienti di O (n 3 ).
risposta data 06.12.2013 - 17:22
fonte
1

Are there any good ways to avoid that? One problem is, if you try to avoid complex if/else statement nesting I tend to do this in one row which can look very terrible and be unreadable and not very easy to maintain.

Usa le funzioni locali per incarnare i corpi di questi condizionali. In questo modo si taglia una linea tra il flusso di lavoro (supervisore) e le transformazioni (esecutori), che migliora la chiarezza.

A prima vista, l'aggiunta di funzioni locali rende la lettura più difficile perché interrompe il flusso di lettura del programma, ma in realtà migliora la chiarezza perché separa il flusso di lavoro dalle trasformazioni.

    
risposta data 06.12.2013 - 12:53
fonte