Dichiarare le funzioni per evitare cicli nidificati espliciti

3

Il mio professore di programmazione mi ha detto che è una buona pratica di programmazione (almeno in C / C ++) dichiarare una funzione con il ciclo interno durante i cicli di annidamento (non i cicli per , da quando, cioè il looping attraverso un array multidimensionale è molto intuitivo). Ad esempio, se scrivo

int main()
{
    while (cond1)
    {
        // ...(1)...
        while (cond2)
        {
            // ...(2)...
        }
    }
}

allora è meglio:

type inner_loop(param)
{
    // ...(1)...

    while (cond2)
    {
        // ...(2)...
    }
}

int main()
{
    while (cond1)
    {
        inner_loop(param);
    }
}

Quali miglioramenti implicano questo metodo? C'è qualche caso in cui questo modo di programmare i loop annidati possa essere controproducente? Grazie in anticipo.

    
posta BdT. 04.02.2013 - 15:41
fonte

8 risposte

18

Aggiunge molta leggibilità a scapito di un piccolo overhead di chiamata di funzione, assumendo che tu scelga un nome migliore di inner_loop . A meno che tu veramente abbia bisogno di ogni grammo di prestazioni, non ci sono molte situazioni in cui non è consigliabile.

Una cosa da notare è che nasconde la complessità del tuo algoritmo. In generale, questa è una buona cosa. Tuttavia, una volta ho visto loop anonimi simili in funzioni creare una funzione O(n^6) da ciò che dovrebbe essere O(n) o migliore, semplicemente perché i manutentori lungo la strada non hanno notato i cicli nidificati quando stavano lavorando sui livelli esterni.

    
risposta data 04.02.2013 - 15:59
fonte
4

Ci sono vantaggi e svantaggi per entrambi gli approcci. Quale approccio preferisci dovrebbe dipendere dallo stile e dalle linee guida del tuo team. Per ora, dal momento che al tuo professore non piace un particolare metodo, non dovresti usarlo per il codice che invii.

Ma non rimanere troppo bloccato su entrambi gli approcci.

La chiamata di una funzione dall'interno del loop semplifica il compito di analizzare il ciclo esterno. Ma aumenta anche il sovraccarico dalla creazione / eliminazione dello stack con un gran numero di chiamate di funzione. Può anche essere più difficile codificare poiché le variabili condivise tra i loop devono essere passate e restituite con ogni chiamata di funzione.

I cicli annidati sono un po 'più veloci da scrivere. Ma possono trascinare la lunghezza della funzione. Le funzioni più lunghe tendono a essere più fragili, il che rende più difficile la manutenzione. Le funzioni più lunghe sono anche più difficili da avvolgere la testa poiché sono ... più lunghe. È un po 'ovvio, ma vale la pena segnalarlo.

C'è un sacco di codice C esistente scritto con cicli annidati senza chiamate di funzioni aggiuntive. Quindi ne vedrai molte in natura. Tuttavia, solo perché "è sempre stato fatto in quel modo" non significa necessariamente che sia il modo giusto .

    
risposta data 04.02.2013 - 16:00
fonte
2

man mano che mi addentro sempre di più nella mia carriera di sviluppo, mi ritrovo a perdere interesse per gli accademici che parlano di livelli di nidificazione, anti-pattern ed ecc.

Condividerò ciò che sembra funzionare bene per me in un ambiente di sviluppo pragmatico e incentrato sull'azienda:

"... risolvi il caso specifico di fronte a te, POI il refattore per il caso generale."

vale a dire. scrivilo "tutto di fila". Quindi inizia ad estrarre cose che sono costrutti ovvi come funzioni. Se stai scrivendo un codice sul mondo reale e sui suoi processi, incontrerai sicuramente molte cose "funzionali" che probabilmente farai diverse volte, se non addirittura "over-and-over" ... estraile, parametrizzale , trasformali in metodi / funzioni che puoi applicare in altri casi.

Ecco un buon modo per valutare se hai un refactoring "fatto": se i tuoi casi di test sembrano tutti molto semplici, diretti. (stai scrivendo casi di test, giusto?) Se i tuoi test sono ovvi e semplici, è probabile che il tuo codice sia strutturato abbastanza bene.

    
risposta data 18.04.2016 - 23:10
fonte
1

Il vantaggio è che il tuo main () è ora più semplice e più facile da leggere (specialmente se il corpo del loop interno è per niente complicato).

Sarebbe uno svantaggio se il ciclo interno fosse molto ovvio, dal momento che richiederebbe uno sforzo leggermente maggiore per leggere qualcosa che non dovrebbe richiedere quasi nessuno sforzo.

Qualsiasi risultato si traduce in un codice più leggibile.

    
risposta data 04.02.2013 - 16:10
fonte
0

Il consiglio più generale è: fai attenzione ai livelli profondi di nidificazione. L'ho sentito chiamare anti-pattern "arrowhead" o "mountain range", perché è così che il codice inizia a sembrare. Se stai nidificando 2 livelli per loop, allora un altro livello per if ... then, e poi if..thens inside the if ... thens, il livello di nidificazione può rendere il codice davvero difficile da seguire.

  • Ho letto un articolo in cui un team ha avuto enormi problemi di manutenzione con il suo codice, quindi ha implementato una politica di massima profondità di nidificazione. Il loro tasso di errore è sceso quasi a zero.

  • Qualcuno famoso una volta ha detto che qualsiasi codice annidato a più di 3 livelli è incomprensibile.

Hm. Non riesco a trovare quella citazione ora, ma ho trovato un articolo correlato su nidificare se ... poi:

link

EDIT: qui è: "se hai bisogno di più di 3 livelli di indentazione, sei fottuto comunque e dovresti correggere il tuo programma."

link

    
risposta data 20.02.2014 - 08:42
fonte
0

Alla fine imparerai a fare il refactoring. Il refactoring consiste nel cambiare un programma in modo che la sua struttura sia diversa, mentre fa esattamente la stessa cosa di prima. Due metodi di refactoring che vengono spesso utilizzati sono la divisione di una funzione in due funzioni e la combinazione di due funzioni in una sola.

L'arte è duplice: in primo luogo, essere in grado di lavorare sul codice focalizzato e refactoring senza introdurre bug. In secondo luogo, per identificare qual è il modo migliore - in questo caso, se il ciclo annidato in una funzione è migliore o i cicli distribuiti su due funzioni.

Affermando che dovresti sempre dividere i cicli annidati in funzioni separate, il tuo professore non ti sta facendo un favore. La divisione aumenta la complessità totale. È vantaggioso se ogni ciclo è così complesso che la nidificazione lo rende complesso troppo (a volte ogni ciclo è già troppo complesso. Combinandoli ti darebbe qualcosa che è molto troppo complesso). È negativo se il ciclo combinato è abbastanza semplice da essere lasciato in pace. Il tuo compito è decidere quale è.

I linguaggi moderni hanno funzionalità che possono effettivamente rimuovere il codice boilerplate e ridurre la complessità dei cicli annidati. Imparare a utilizzare queste funzionalità ti porterà molto più avanti rispetto alla complessità del tuo codice e alla sua suddivisione.

E dov'è il ciclo spaccato controproducente? Il fatto è che un computer moderno probabilmente gestirà qualsiasi cosa tu passi con facilità, tranne i cicli annidati. Scorrere fino a 10.000 articoli, prendendo 1 microsecondo per ciascuno - l'utente non si accorge nemmeno. Scorri fino a 10.000 volte 10.000 elementi e impiega 100 secondi. In molti casi deve trovare un modo migliore di un ciclo annidato (se non è possibile, chiedere su StackOverflow).

Con il ciclo annidato suddiviso su due funzioni, hai una funzione che chiama una funzione in un ciclo diretto - difficile vedere come migliorarla. E hai una funzione con un semplice ciclo - difficile vedere come migliorarlo. Qualsiasi miglioramento deve considerare la combinazione di entrambi e hai interrotto la connessione. Ciò rende più difficile vedere che è necessario migliorare l'efficienza del tuo codice e che può essere fatto.

    
risposta data 18.04.2016 - 23:58
fonte
0

In teoria, dichiarare le funzioni per i tuoi loop annidati può suonare bene. In pratica devi stare molto attento a nascondere la complessità.

Forse posso spiegare meglio con un esempio. Considera di avere una lista. Ora crei una funzione di aggiunta, ma non vuoi duplicati, quindi la funzione di aggiunta controlla tutti gli elementi e aggiunge un elemento solo se non ci sono duplicati. Abbastanza facile, i computer sono veloci, non ci vorrà molto tempo nei test.

Quindi inizi con una lista vuota e aggiungi elementi uno per uno. Abbastanza facile, va veloce nel test.

Mezzo anno in cui ricevi lamentele da parte dei tuoi clienti sul fatto che la tua app è lenta, solo perché beh, ci sono 10.000 elementi aggiunti alla tua lista e perché hai nascosto la complessità, non ti sei visto fare un (n ^ 2 ) situazione.

La complessità deve essere chiara o altrimenti ti morderà.

    
risposta data 19.04.2016 - 10:22
fonte
-2

Per diversi scenari sono considerati diversi approcci. Quando vogliamo inviare un codice che è facile da capire dall'insegnante o da qualsiasi altro amico, dobbiamo assolutamente evitare di usare cicli annidati, poiché diventa difficile per il controllore tracciare le chiamate per tutte le variabili una alla volta. Ma se non esiste un caso del genere, non vi è alcun danno nell'usare i cicli annidati poiché non vi è alcun cambiamento nelle complessità. Effettuare ulteriori chiamate funzionali aumenta solo il numero dei record di attivazione utilizzati per le diverse funzioni e anche la memoria richiesta per il puntatore del record di attivazione corrente e gli altri puntatori che puntano ai record precedenti delle funzioni aumenta.Così in entrambi i modi in cui la memoria aumenta ma spetta al programmatore quale approccio usare.Ma il preferito è creare funzioni per scopi diversi, poiché diventa facile per il programmatore anche capire il codice dopo se lo rivede dopo un po 'di tempo.

    
risposta data 05.02.2013 - 08:45
fonte

Leggi altre domande sui tag