Le funzioni lunghe sono accettabili se hanno una struttura interna?

23

Quando si affrontano algoritmi complicati in linguaggi con supporto per funzioni annidate (come Python e D) spesso scrivo funzioni enormi (perché l'algoritmo è complicato) ma lo mitigo usando funzioni nidificate per strutturare il codice complicato. Le enormi funzioni (100+ linee) sono ancora considerate malvagie anche se sono ben strutturate internamente tramite l'uso di funzioni annidate?

Modifica: per coloro che non hanno familiarità con Python o D, le funzioni nidificate in questi linguaggi consentono anche l'accesso all'ambito della funzione esterna. In D questo accesso consente la mutazione di variabili nell'ambito esterno. In Python consente solo la lettura. In D puoi disabilitare esplicitamente l'accesso all'ambito esterno in una funzione annidata dichiarandolo static .

    
posta dsimcha 28.09.2010 - 00:18
fonte

12 risposte

20

Ricorda sempre la regola, una funzione fa una cosa e fa bene! Se puoi farlo, evita le funzioni annidate.

Ostacola la leggibilità e i test.

    
risposta data 28.09.2010 - 07:01
fonte
11

Alcuni hanno sostenuto che le brevi funzioni possono essere più soggette a errori rispetto alle funzioni lunghe .

Card and Glass (1990) point out that the design complexity really involves two aspects: the complexity within each component and the complexity of the relationships among components.

Personalmente, ho trovato che il codice rettilineo ben commentato è più facile da seguire (specialmente quando non sei stato tu a scriverlo originariamente) rispetto a quando è suddiviso in più funzioni che non sono mai state usate altrove. Ma dipende davvero dalla situazione.

Penso che il principale take-away sia che quando dividi un blocco di codice, stai scambiando un tipo di complessità con un altro. C'è probabilmente un punto debole da qualche parte nel mezzo.

    
risposta data 28.09.2010 - 22:25
fonte
9

Idealmente, l'intera funzione dovrebbe essere visibile senza dover scorrere. A volte, questo non è possibile. Ma se riesci a scomporlo, renderà molto più semplice la lettura del codice.

So che non appena spingo Pagina su / giù o si passa a una sezione diversa del codice, posso solo ricordare 7 +/- 2 elementi dalla pagina precedente. E sfortunatamente, alcune di queste posizioni verranno utilizzate durante la lettura del nuovo codice.

Mi piace sempre pensare alla mia memoria a breve termine come i registri di un computer (CISC, non RISC). Se hai l'intera funzione sulla stessa pagina, puoi andare in cache per ottenere le informazioni richieste da un'altra sezione del programma. Se l'intera funzione non si adatta a una pagina, sarebbe l'equivalente di spingere sempre qualsiasi memoria sul disco dopo ogni operazione.

    
risposta data 28.09.2010 - 23:38
fonte
4

Perché utilizzare funzioni annidate, piuttosto che normali funzioni esterne?

Anche se le funzioni esterne sono sempre e solo usate nella tua funzione, una volta grande, rende ancora più facile leggere tutto il casino:

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}
    
risposta data 28.09.2010 - 06:44
fonte
3

Non ho il libro di fronte a me proprio in questo momento (per citare), ma secondo il Codice Completo il "sweetspot" per la durata della funzione era di circa 25-50 righe di codice secondo la sua ricerca.

Ci sono momenti in cui va bene avere funzioni lunghe:

  • Quando la complessità ciclomatica della funzione è bassa. I tuoi colleghi sviluppatori potrebbero sentirsi un po 'frustrati se devono esaminare una funzione che contiene un'enunciazione gigante if e la dichiarazione else che se non è sullo schermo allo stesso tempo.

Le volte in cui non è normale avere funzioni lunghe:

  • Hai una funzione con condizionali profondamente nidificati. Fai un favore ai tuoi colleghi lettori di codice, migliora la leggibilità interrompendo la funzione. Una funzione fornisce un'indicazione al suo lettore che "Questo è un blocco di codice che fa una cosa". Inoltre, chiediti se la lunghezza della funzione indica che sta facendo troppo e che deve essere scomposta in un'altra classe.

La linea di fondo è che la manutenibilità dovrebbe essere una delle massime priorità del tuo elenco. Se un altro dev non può guardare il tuo codice e ottenere un "succo" di ciò che il codice sta facendo in meno di 5 secondi, il codice ur non fornisce abbastanza "metadati" per dire cosa sta facendo. Altri sviluppatori dovrebbero essere in grado di dire cosa sta facendo la tua classe semplicemente guardando il browser degli oggetti nel tuo IDE scelto invece di leggere più di 100 righe di codice.

Le funzioni più piccole hanno i seguenti vantaggi:

  • Portabilità: è molto più semplice spostare le funzionalità (all'interno della classe sul refactoring di una diversa)
  • Debug: quando guardi lo stacktrace è molto più rapido individuare un errore se stai cercando una funzione con 25 righe di codice anziché 100.
  • Leggibilità - Il nome della funzione dice cosa sta facendo un intero blocco di codice. Un dev sulla tua squadra potrebbe non voler leggere quel blocco se non lavora con esso. Inoltre, nella maggior parte degli IDE moderni un altro dev può comprendere meglio cosa sta facendo la tua classe leggendo i nomi delle funzioni in un browser degli oggetti.
  • Navigazione - La maggior parte degli IDE ti consente di cercare il nome delle funzioni. Inoltre, i più moderni IDE hanno la possibilità di vedere la fonte di una funzione in un'altra finestra, questo dà agli altri sviluppatori la possibilità di guardare la tua funzione lunga su 2 schermi (se i monitor multipli) invece di farli scorrere.

L'elenco continua .....

    
risposta data 29.09.2010 - 19:40
fonte
2

La risposta dipende, tuttavia dovresti probabilmente trasformarla in una classe.

    
risposta data 28.09.2010 - 02:36
fonte
2

Non mi piace la maggior parte delle funzioni annidate. Lambdas rientra in questa categoria ma di solito non mi contrassegna se non ha più di 30-40 caratteri.

Il motivo principale è che diventa una funzione densamente localmente localizzata con ricorsione semantica interna, che significa che è difficile per me avvolgere il mio cervello, ed è solo più facile spingere alcune cose ad un funzione di supporto che non ingombra lo spazio del codice.

Ritengo che una funzione dovrebbe fare la sua cosa. Fare altre cose è ciò che fanno le altre funzioni. Quindi se hai una funzione da 200 righe Doing Its Thing e tutto scorre, è A-OK.

    
risposta data 28.09.2010 - 23:27
fonte
1

È accettabile? Questa è davvero una domanda a cui solo tu puoi rispondere. La funzione raggiunge ciò di cui ha bisogno? È mantenibile? È 'accettabile' per gli altri membri della tua squadra? Se è così, allora è quello che conta davvero.

Modifica: non ho visto nulla riguardo le funzioni annidate. Personalmente, non li userei. Io userei invece le normali funzioni.

    
risposta data 28.09.2010 - 07:55
fonte
1

Una lunga funzione "linea retta" può essere un modo chiaro per specificare una lunga sequenza di passaggi che si verificano sempre in una particolare sequenza.

Tuttavia, come altri hanno menzionato, questa forma è incline ad avere tratti locali di complessità in cui il flusso generale è meno evidente. Posizionare quella complessità locale in una funzione annidata (vale a dire, definita altrove all'interno della funzione lunga, forse nella parte superiore o inferiore) può ripristinare la chiarezza sul flusso della linea principale.

Una seconda considerazione importante è il controllo dell'ambito delle variabili che devono essere utilizzate solo in un tratto locale di una funzione lunga. La vigilanza è necessaria per evitare che una variabile introdotta in una sezione di codice venga rinviata in modo non intenzionale altrove (ad esempio, dopo cicli di modifica), poiché questo tipo di errore non verrà visualizzato come errore di compilazione o di runtime.

In alcune lingue, questo problema è facilmente evitato: una porzione locale di codice può essere avvolta in un suo blocco, come con "{...}", all'interno del quale qualsiasi variabile appena introdotta è visibile solo a quel blocco . Alcune lingue, come Python, non dispongono di questa funzionalità, nel qual caso le funzioni locali possono essere utili per applicare regioni di ambito più piccole.

    
risposta data 26.02.2014 - 01:39
fonte
1

No, le funzioni di più pagine non sono desiderabili e non dovrebbero superare la revisione del codice. Ho anche usato funzioni lunghe, ma dopo aver letto il Refactoring di Martin Fowler, I fermato. Le funzioni lunghe sono difficili da scrivere correttamente, difficili da capire e difficili da testare. Non ho mai visto una funzione anche di 50 linee che non sarebbero state più facilmente comprese e testate se fossero state suddivise in una serie di funzioni più piccole. In una funzione a più pagine ci sono quasi certamente intere classi che dovrebbero essere prese in considerazione. È difficile essere più specifici. Forse dovresti pubblicare una delle tue lunghe funzioni su Code review e qualcuno (forse io) può mostrarti come migliorarlo.

    
risposta data 26.02.2014 - 06:43
fonte
0

Quando sto programmando in python, mi piace fare un passo indietro dopo aver scritto una funzione e chiedermi se aderisce allo "Zen di Python" (digita "importa questo" nell'interprete python):

Bello è meglio che brutto.
 L'esplicito è migliore di quello implicito.
 Semplice è meglio del complesso.
 Complesso è meglio che complicato.
Appartamento è meglio di nidificato.  Sparse è meglio che denso.
 Contabilità.
 I casi speciali non sono abbastanza speciali da infrangere le regole.
 Anche se la praticità batte la purezza.
 Gli errori non dovrebbero mai passare in silenzio.
 Salvo esplicitamente tacere.
 Di fronte all'ambiguità, rifiuta la tentazione di indovinare.
 Ci dovrebbe essere uno - e preferibilmente solo un modo - ovvio per farlo.
 Anche se in quel modo potrebbe non essere ovvio all'inizio, a meno che tu non sia olandese.
 Ora è meglio che mai.
 Anche se non è mai spesso meglio di right adesso.
 Se l'implementazione è difficile da spiegare, è una cattiva idea.
 Se l'implementazione è facile da spiegare, potrebbe essere una buona idea.
 Gli spazi dei nomi sono una delle grandi idee - facciamo di più!

    
risposta data 18.02.2011 - 20:08
fonte
0

Inseriscili in un modulo separato.

Supponendo che la tua soluzione non sia più gonfia del necessario non hai troppe opzioni. Hai già suddiviso le funzioni in diverse sottofunzioni, quindi la domanda è dove dovresti metterla:

  1. Inseriscili in un modulo con una sola funzione "pubblica".
  2. Inseriscili in una classe con una sola funzione "pubblica" (statica).
  3. Annidali in una funzione (come hai descritto).

Ora sia la seconda che la terza alternativa sono in qualche modo nidificanti, ma la seconda alternativa potrebbe non essere male per alcuni programmatori. Se non escludi la seconda alternativa, non vedo troppi motivi per escludere il terzo.

    
risposta data 12.06.2015 - 12:28
fonte