Quale dovrebbe essere la lunghezza massima di una funzione?
E ci sono casi che rappresentano un'eccezione?
Quale dovrebbe essere la lunghezza massima di una funzione?
E ci sono casi che rappresentano un'eccezione?
Da quando ho intrapreso questo pazzo racket nel 1970, ho visto esattamente un modulo che veramente necessario per essere più di una pagina stampata (circa 60 righe). Ho visto molti moduli più lunghi.
Peraltro, ho scritto moduli che erano più lunghi, ma di solito erano grandi macchine a stati finiti scritti come grandi dichiarazioni di commutazione.
Parte del problema sembra essere che ai programmatori in questi giorni non viene insegnato a modulare le cose.
Anche gli standard di codifica che massimizzano lo spreco di spazio verticale fanno parte del problema. (Ho ancora per incontrare un gestore di software che ha letto Gerald Weinberg 's " Psicologia della programmazione informatica ". Weinberg sottolinea che diversi studi hanno dimostrato che la comprensione del programmatore è essenzialmente limitata a ciò che il programmatore può vedere in qualsiasi momento Se il programmatore deve scorrere o girare una pagina, la loro comprensione diminuisce in modo significativo: devono ricordare e astrarre.)
Rimango convinto che molti dei guadagni di produttività del programmatore ben documentati da FORTH sono dovuti a il sistema di "blocco" FORTH per il codice sorgente: i moduli erano rigidamente limitati a un massimo assoluto di 16 righe di 64 caratteri. Puoi contare infinitamente, ma non puoi in nessuna circostanza scrivere una routine di 17 righe.
Dipende dal linguaggio che usi, ma in generale (e per i miei gusti personali):
Se è di più, è qualcosa di cui ho bisogno per tornare più tardi e rielaborare.
Ma realisticamente , qualsiasi dimensione deve essere quando devi consegnare qualcosa e che ha più senso sul momento di sputarli in quel modo, a volte rende ancora più facile la revisione prima della spedizione. (ma torniamo indietro più tardi)
(Recentemente il mio team ha eseguito un programma sulla nostra base di codice: abbiamo trovato la classe con 197 metodi e un'altra con solo 3 metodi, ma uno di questi era 600. Gioco carino: qual è il peggio dei 2 mali?)
Ora per una risposta più zen ... In generale è considerata una buona pratica (TM) citare uno o due grandi uomini, quindi ecco qui:
Everything should be made as simple as possible, but not simpler. - A. Einstein
Perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away. - A. de Saint Exupéry
Come addendum a questo, le tue funzioni dovrebbero avere nomi chiari che spieghino il loro intento. Per quanto riguarda i commenti, di solito non commento all'interno di una funzione:
Un blocco di commenti nella parte superiore di ogni funzione (che richiede una spiegazione) è sufficiente. Se la tua funzione è piccola e i nomi delle funzioni sono abbastanza espliciti, allora dovresti solo dire cosa vuoi ottenere e perché. Uso i commenti incorporati solo per i campi in alcune lingue o all'avvio di blocchi per le funzioni che interrompono le regole della linea 25-35 se l'intento non è chiaro. Uso un commento di blocco all'interno del codice quando si verifica una situazione eccezionale (un blocco catch in cui non è necessario o si desidera fare qualcosa dovrebbe avere un commento che spieghi perché, ad esempio).
Per ulteriori informazioni, leggi la mia risposta su Stile e raccomandazioni del codice di commento
Secondo me, ogni funzione dovrebbe essere il più piccola possibile. Ogni funzione dovrebbe fare solo una cosa e farlo bene. Questo in realtà non risponde alla domanda di lunghezza massima, ma sono più i miei sentimenti sulla lunghezza delle funzioni.
Per usare le parole di Uncle Bob, "Estrai fino a non puoi più estrarre altro. Estratto fino allo sfinimento. "
Quale dovrebbe essere l'altezza massima di un edificio? Dipende da dove si trova la build o dall'altezza che vuoi che sia.
Puoi ottenere risposte diverse da persone diverse che provengono da città diverse.
Alcune funzioni di script e gestori di interrupt del kernel sono molto lunghi.
Un metodo che funziona per me è: Posso avere una parte di una funzione più lunga dare un nome che abbia un senso. Penso che la lunghezza di un metodo non sia così importante come una buona nomenclatura. Il metodo dovrebbe fare ciò che dice il nome, né più né meno. E dovresti essere in grado di dare un buon nome. Se non riesci a denominare correttamente il tuo metodo, probabilmente il codice non è buono.
Finché deve essere necessario fare ciò che deve fare, ma non più.
Penso che ci sia un compromesso. Se disponi di molti metodi brevi, è spesso più difficile eseguirne il debug rispetto a un metodo lungo. Se devi saltare intorno all'editor 20 o 30 volte per rintracciare una chiamata al metodo, sarà difficile tenere tutto in testa. Nel frattempo, se esiste un metodo chiaro ben scritto, anche se è 100 righe, è spesso più facile tenere a mente.
La vera domanda è: perché gli oggetti dovrebbero essere in metodi diversi, e la risposta data sopra è il riutilizzo del codice. Se non stai riutilizzando il codice (o non lo sai), allora potrebbe essere sensato lasciarlo in un unico metodo facile da seguire e poi, quando devi riutilizzarlo, dividere le parti che necessitano utilizzando in metodi più piccoli.
In realtà, parte del buon metodo di progettazione sta facendo metodi funzionalmente coesivi (essenzialmente fanno una cosa sola). La lunghezza dei metodi non ha importanza. Se una funzione ha una cosa ben definita ed è di 1000 righe, è un buon metodo. Se una funzione fa 3 o 4 cose e ha solo 15 linee, allora è un metodo sbagliato ...
Trovo più facile tenere traccia di ciò che sto facendo se riesco a vedere l'intera funzione tutto in una volta. Ecco come preferisco scrivere le funzioni:
Raramente scrivo funzioni più lunghe di così. La maggior parte di questi sono gigantesche istruzioni switch C / C ++.
La domanda dovrebbe essere quante cose dovrebbe fare una funzione. E di solito, è raro che tu abbia bisogno di 100 righe per fare "una cosa". Di nuovo ciò dipende dal livello da cui stai guardando il codice: hashing una password una cosa? Oppure l'hashing e il salvataggio della password una cosa?
Direi, iniziare con il salvataggio della password come una funzione. Quando senti che l'hashing è diverso e rifatti il codice. Non sono un programmatore esperto con qualsiasi mezzo, ma IMHO, l'idea generale delle funzioni inizia piccola è che più le tue funzioni sono atomiche, maggiore è la possibilità di riutilizzo del codice, non dovendo mai effettuare lo stesso cambiamento in più di un posto , ecc.
Ho visto SQL stored procedure con oltre 1000 righe. Il numero di righe delle stored procedure è inferiore a 50? Non lo so, ma rende leggendo il codice, diavolo. Non solo devi continuare a scorrere su e giù, devi dare a poche righe di codice un nome come "questo fa validazione1", "questo aggiorna nel database", ecc. - un lavoro che il programmatore avrebbe dovuto fare.
Da complessità Cyclomatic (Wikipedia):
The cyclomatic complexity of a section of source code is the count of the number of linearly independent paths through the source code.
Ti consiglio di mantenere quel numero inferiore a 10 in un unico metodo. Se arriva a 10, allora è il momento di ridimensionare.
Esistono strumenti in grado di valutare il tuo codice e fornirti un numero di complessità ciclomatica.
Dovresti sforzarti di integrare questi strumenti nella tua pipeline di costruzione.
Non inseguire letteralmente le dimensioni del metodo, ma prova a considerarne la complessità e le responsabilità. Se ha più di una responsabilità, allora è probabilmente una buona idea ripensare. Se la sua complessità ciclomatica aumenta, allora è probabilmente il momento di ridimensionare.
Sono abbastanza sicuro che ci siano altri strumenti che ti danno un feedback simile, ma non ho ancora avuto la possibilità di esaminare questo aspetto.
In genere, cerco di mantenere i miei metodi / funzioni su ciò che si adatta allo schermo di un monitor 1680x1050. Se non si adatta, utilizza i metodi / le funzioni di supporto per suddividere l'attività.
Aiuta la leggibilità su schermo e su carta.
Abbastanza breve da essere ottimizzato correttamente
I metodi dovrebbero essere così brevi da fare esattamente una cosa. Il motivo è semplice: quindi il tuo codice può essere ottimizzato correttamente.
In un linguaggio JIT-ted come Java o C #, è importante che i tuoi metodi siano semplici in modo che il compilatore JIT possa produrre rapidamente il codice. Metodi più lunghi e più complicati richiedono naturalmente più tempo JIT. Inoltre, i compilatori JIT offrono solo una serie di ottimizzazioni e solo i metodi più semplici ne traggono vantaggio. Questo fatto è stato persino richiamato in C # di Bill Wagner
In un linguaggio di livello inferiore, come C o C ++, avere anche metodi brevi (forse una dozzina di righe) è importante perché in questo modo minimizzi la necessità di memorizzare le variabili locali nella RAM piuttosto che in un registro. (Aka 'Register Spilling'.) Si noti tuttavia che in questo caso non gestito, il costo relativo di ogni chiamata di funzione può essere piuttosto elevato.
E anche in un linguaggio dinamico, come Ruby o Python, avere metodi brevi aiuta anche nell'ottimizzazione del compilatore. In un linguaggio dinamico, più una funzione è "dinamica", più è difficile ottimizzare. Ad esempio, un metodo lungo che accetta una X e potrebbe restituire un Int, Float o String probabilmente eseguirà molto più lentamente di tre metodi separati, ognuno dei quali restituirà un solo tipo. Questo perché, se il compilatore sa esattamente quale tipo restituirà la funzione, può ottimizzare anche il sito di chiamata di funzione. (Ad esempio, non controllare le conversioni di tipo.)
Per me, una funzione è qualsiasi lunghezza che deve essere. Il più delle volte che lo divido è quando riutilizzerò il codice.
Fondamentalmente, seguirò il principio "alta coesione, basso accoppiamento" e non ci sono limiti di lunghezza.
Non metto un limite alla linea dura su nulla perché alcune funzioni implementano algoritmi che sono intrinsecamente complessi e qualsiasi tentativo di renderli più brevi renderebbe le interazioni tra le nuove funzioni più brevi così complicate che il risultato netto non sarebbe una riduzione nella semplicità. Inoltre, non credo che l'idea che una funzione debba fare solo "una cosa" è una buona guida, poiché "una cosa" ad alto livello di astrazione può essere "molte cose" a un livello inferiore.
Per me, una funzione è decisamente troppo lunga se la sua lunghezza causa violazioni sottili di DRY al momento, e l'estrazione di una parte della funzione in una nuova funzione o classe potrebbe risolvere questo problema. Una funzione può essere troppo lunga se questo non è il caso, ma una funzione o una classe potrebbe essere facilmente estratta che renderebbe il codice più modulare in un modo che rischia di essere utile di fronte a cambiamenti prevedibili lungo la strada. / p>
Dipende molto da cosa c'è nel codice.
Ho visto una routine di migliaia di righe con cui non ho avuto problemi. Era un'enorme dichiarazione di commutazione, nessuna opzione superava una dozzina di righe e l'unica struttura di controllo in ogni opzione era un singolo ciclo. In questi giorni sarebbe stato scritto con gli oggetti, ma quella non era un'opzione allora.
Sto anche guardando 120 linee in un interruttore di fronte a me. Nessun caso supera 3 linee: una guardia, un incarico e la rottura. Sta analizzando un file di testo, gli oggetti non sono una possibilità. Qualsiasi alternativa sarebbe più difficile da leggere.
La maggior parte dei compilatori non si preoccupa della lunghezza di una funzione. Una funzione dovrebbe essere funzionale, ma essere sia facile da capire, cambiare e riutilizzare per gli esseri umani. Scegli una lunghezza che ti si addice di più.
La mia regola generale è che una funzione dovrebbe adattarsi allo schermo. Ci sono solo tre casi che ho trovato che tendono a violare questo:
1) Funzioni di spedizione. Ai vecchi tempi questi erano comuni, ma la maggior parte di questi sono sostituiti con l'ereditarietà oggettuale di questi tempi. Gli oggetti funzionano solo all'interno del tuo programma, tuttavia, e quindi vedrai comunque occasionali funzioni di invio quando gestisci dati provenienti da altrove.
2) Funzioni che fanno un sacco di passaggi per raggiungere un obiettivo e dove i passaggi mancano di una buona suddivisione. Si finisce con una funzione che chiama semplicemente una lunga lista di altre funzioni nell'ordine.
3) Come il # 2, ma in cui i singoli passaggi sono così piccoli che sono semplicemente in linea anziché chiamati separatamente.
Forse la durata della funzione non è così una buona metrica. Cerchiamo di utilizzare la complessità ciclomatica , anche sui metodi, e uno dei controlli di controllo del codice sorgente futuro regola la complessità ciclomatica sulle classi e i metodi devono essere inferiori a X.
Per i metodi, X è impostato su 30, ed è abbastanza stretto.
Leggi altre domande sui tag programming-practices coding-standards