Qual è la lunghezza ideale di un metodo per te? [chiuso]

110

Nella programmazione orientata agli oggetti, ovviamente non esiste una regola esatta sulla lunghezza massima di un metodo, ma ho comunque trovato queste due citazioni in qualche modo in contraddizione tra loro, quindi mi piacerebbe sentire cosa ne pensate.

In Codice pulito: un manuale di abilità software agile , Robert Martin dice :

The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that. Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.

e fornisce un esempio dal codice Java che vede da Kent Beck:

Every function in his program was just two, or three, or four lines long. Each was transparently obvious. Each told a story. And each led you to the next in a compelling order. That’s how short your functions should be!

Questo suona alla grande, ma d'altra parte, in Codice completo , Steve McConnell dice qualcosa di molto diverso:

The routine should be allowed to grow organically up to 100-200 lines, decades of evidence say that routines of such length no more error prone then shorter routines.

E dà un riferimento a uno studio che dice che le routine di 65 linee o lunghe sono più economiche da sviluppare.

Quindi, anche se ci sono opinioni divergenti sulla questione, esiste per te una best practice funzionale?

    
posta iPhoneDeveloper 05.02.2012 - 11:26
fonte

14 risposte

105

Le funzioni dovrebbero normalmente essere brevi, tra 5-15 linee è la mia personale "regola del pollice" quando si scrive in Java o C #. Questa è una buona dimensione per diversi motivi:

  • Si adatta facilmente sullo schermo senza scorrere
  • Si tratta della dimensione concettuale che puoi tenere in testa
  • È abbastanza significativo da richiedere una funzione a sé stante (come un pezzo di logica autonomo e significativo)
  • Una funzione più piccola di 5 linee è un suggerimento che forse stai infrangendo troppo il codice (il che rende più difficile leggere / capire se devi navigare tra le funzioni). O quello o stai dimenticando i casi speciali / gestione degli errori!

Ma non penso sia utile impostare una regola assoluta, poiché ci saranno sempre eccezioni / motivi validi per divergere dalla regola:

  • Una funzione di accesso a una riga che esegue un cast di tipi è chiaramente accettabile in alcune situazioni.
  • Ci sono alcune funzioni molto brevi ma utili (ad esempio swap come menzionato dall'utente sconosciuto) che hanno chiaramente bisogno di meno di 5 righe. Non un grosso problema, alcune funzioni di 3 linee non danneggiano il tuo codice base.
  • Una funzione a 100 righe che è una singola istruzione switch di grandi dimensioni potrebbe essere accettabile se è estremamente chiaro ciò che viene fatto. Questo codice può essere concettualmente molto semplice anche se richiede molte linee per descrivere i diversi casi. A volte si suggerisce che questo dovrebbe essere rifattorizzato in classi separate e implementato usando l'ereditarietà / polimorfismo ma IMHO questo sta prendendo OOP troppo lontano - preferirei avere una grande dichiarazione di switch a 40 vie di 40 nuove classi da affrontare.
  • Una funzione complessa potrebbe avere un sacco di variabili di stato che diventerebbero molto confuse se passate tra diverse funzioni come parametri. In questo caso si potrebbe ragionevolmente sostenere che il codice è più semplice e facile da seguire se si tiene tutto in un'unica grande funzione (anche se come sottolinea giustamente Marco potrebbe anche essere un candidato per trasformarsi in una classe per incapsulare sia la logica e stato)
  • A volte funzioni più piccole o più grandi hanno dei vantaggi in termini di prestazioni (forse a causa di motivi di integrazione o di JIT come menziona Frank). Questo dipende molto dall'implementazione, ma può fare la differenza: assicurati di avere un punto di riferimento!

In pratica, usa il buon senso , attenersi a dimensioni di funzione ridotte nella maggior parte dei casi, ma non essere dogmatico se hai una buona ragione per fare un funzione insolitamente grande.

    
risposta data 05.02.2012 - 11:44
fonte
28

Anche se sono d'accordo con i commenti degli altri quando hanno detto che non esiste una regola rigida sul giusto numero di LOC, scommetto se guardiamo indietro ai progetti che abbiamo guardato in passato e identifichiamo ogni funzione sopra, diciamo 150 righe di codice , Suppongo che avremmo raggiunto il consenso sul fatto che 9 su 10 di queste funzioni interrompono l'SRP (e molto probabilmente anche l'OCP), hanno troppe variabili locali, troppi flussi di controllo e sono generalmente difficili da leggere e conservare. / p>

Quindi, mentre LOC potrebbe non essere un indicatore diretto di codice errato, è certamente un indicatore indiretto decente che una certa funzione potrebbe essere scritta meglio.

Nella mia squadra sono caduto nella posizione di protagonista e, qualunque sia la ragione, le persone sembrano ascoltarmi. Quello su cui mi sono basato in genere è dire al team che mentre non c'è un limite assoluto, qualsiasi funzione più di 50 linee di codice dovrebbe sollevare almeno una bandiera rossa durante la revisione del codice, in modo da dargli una seconda occhiata e rivalutarla per complessità e violazioni SRP / OCP. Dopo quella seconda occhiata, potremmo lasciarla da sola o potremmo cambiarla, ma almeno fa riflettere la gente su queste cose.

    
risposta data 06.02.2012 - 04:36
fonte
19

Sono entrato in un progetto che non ha avuto alcuna attenzione alle linee guida sulla codifica. Quando guardo il codice, a volte trovo le classi con più di 6000 righe di codice e meno di 10 metodi. Questo è uno scenario horror quando devi correggere i bug.

Una regola generale di quanto un metodo grande dovrebbe essere al massimo a volte non è così buono. Mi piace la regola di Robert C. Martin (Zio Bob): "I metodi dovrebbero essere piccoli, più piccoli dei piccoli". Cerco sempre di usare questa regola. Sto cercando di mantenere i miei metodi semplici e piccoli chiarendo che il mio metodo fa solo una cosa e niente di più.

    
risposta data 05.02.2012 - 12:15
fonte
10

Non si tratta di numero di linee, si tratta di SRP. Secondo questo principio, il tuo metodo dovrebbe fare una sola cosa.

Se il tuo metodo fa questo AND questo AND questo O that = > probabilmente sta facendo molto. Prova a guardare questo metodo e analizza: "qui ottengo questi dati, lo ordino e ottengo elementi di cui ho bisogno" e "qui elaboro questi elementi" e "qui finalmente li combino per ottenere il risultato". Questi "blocchi" dovrebbero essere refactored ad altri metodi.

Se segui semplicemente SRP la maggior parte del tuo metodo sarà piccola e con chiara intenzione.

Non è corretto dire "questo metodo è > 20 linee quindi è sbagliato". Può essere un'indicazione che qualcosa potrebbe essere sbagliato con questo metodo, non di più.

Potresti avere un cambio di 400 linee in un metodo (spesso accade nelle telecomunicazioni), ed è ancora una singola responsabilità ed è perfettamente OK.

    
risposta data 05.02.2012 - 13:51
fonte
8

Penso che un problema qui sia che la lunghezza di una funzione non dice nulla sulla sua complessità. LOC (Linee di codice) sono un cattivo strumento per misurare qualsiasi cosa.

Un metodo non dovrebbe essere eccessivamente complesso, ma ci sono scenari in cui un metodo lungo può essere facilmente mantenuto. Nota che il seguente esempio non dice che non può essere diviso in metodi, solo che i metodi non cambierebbero la manutenibilità.

ad esempio un gestore per i dati in entrata può avere una dichiarazione switch di grandi dimensioni e quindi un codice semplice per caso. Ho un tale codice: gestire i dati in arrivo da un feed. 70 (!) Gestori di codici numerici. Ora, si dirà "usa le costanti" - sì, tranne che l'API non le fornisce e mi piace stare vicino alla "fonte" qui. Metodi? Certo, purtroppo tutti si occupano dei dati delle stesse 2 enormi strutture. Nessun beneficio nel dividerli, tranne forse con più metodi (leggibilità). Il codice non è intrinsecamente complesso: un interruttore, a seconda del campo. Quindi ogni caso ha un blocco che analizza x elementi di dati e li pubblica. Nessun incubo di manutenzione. C'è una ripetizione "se la condizione che determina se un campo ha dati (pField = pFields [x], se pField- > IsSet () {blabla}) - lo stesso praticamente per ogni campo ...

Sostituisci quello con una routine molto più piccola che contiene un ciclo annidato e un sacco di istruzioni di commutazione reali e un metodo enorme può essere più facile da gestire rispetto a uno più piccolo.

Quindi, mi dispiace, LOC non è una buona misura per iniziare. Se possibile, utilizzare i punti di complessità / decisione.

    
risposta data 05.02.2012 - 11:49
fonte
8

Dipende , seriamente, non c'è davvero una risposta solida a questa domanda perché la lingua con cui lavori è importante, le cinque-quindicesime righe menzionate in questa risposta potrebbe funzionare per C # o Java, ma in altre lingue non ti dà molto lavoro. Allo stesso modo, a seconda del dominio in cui stai lavorando, potresti trovarti a scrivere valori di impostazione del codice in una struttura di dati di grandi dimensioni. Con alcune strutture dati potresti avere decine di elementi che devi impostare, nel caso dovessi suddividere le funzioni in funzioni separate solo perché la tua funzione sta funzionando a lungo?

Come altri hanno notato, la migliore regola empirica è che una funzione dovrebbe essere una singola entità logica che gestisce una singola attività. Se provi a far rispettare le regole draconiane che dicono che le funzioni non possono essere più lunghe delle righe n e rendi quel valore troppo piccolo il tuo codice diventerà più difficile da leggere mentre gli sviluppatori tentano di usare trucchi fantasiosi per aggirare la regola. Allo stesso modo, se lo si imposta troppo alto sarà un non-problema e può portare a codice cattivo anche se la pigrizia. La soluzione migliore è semplicemente condurre revisioni del codice per garantire che le funzioni gestiscano un singolo compito e lasciatelo.

    
risposta data 06.02.2012 - 05:01
fonte
5

Se trovo un metodo lungo, potrei scommettere che questo metodo non è testato in modo corretto per l'unità o la maggior parte del tempo non ha affatto un test unitario. Se inizi a fare TDD non costruirai mai metodi a 100 righe con 25 diverse responsabilità e 5 loop annidati. I test ti obbligano a rifattorizzare costantemente il tuo casino ea scrivere il codice di Bob dello zio.

    
risposta data 11.04.2013 - 22:16
fonte
5

Sono stato in questa pazza racket, in un modo o nell'altro, dal 1970.

In tutto questo tempo, con due eccezioni che raggiungerò tra un momento, non ho MAI visto una "routine" ben progettata (metodo, procedura, funzione, subroutine, qualunque) che NECESSITA di essere più di una lunga pagina stampata (circa 60 righe). La stragrande maggioranza di loro era piuttosto breve, dell'ordine di 10-20 linee.

Tuttavia, ho visto un sacco di codice "stream-of-consciousness", scritto da persone che apparentemente non hanno mai sentito parlare di modularizzazione.

Le due eccezioni erano casi molto speciali. Uno è in realtà una classe di casi di eccezione, che io raggruppo insieme: grandi automi a stati finiti, implementati come grandi dichiarazioni di switch brutti, di solito perché non c'è un modo più pulito per implementarli. Queste cose di solito compaiono in apparecchiature di test automatizzate, analizzando i registri dei dati dal dispositivo sotto test.

L'altra era la routine dei siluri fotonici del gioco STARTRK di Matuszek-Reynolds-McGehearty-Cohen, scritto in CDC 6600 FORTRAN IV. Doveva analizzare la linea di comando, quindi simulare il volo di ogni siluro, con perturbazioni, controllare l'interazione tra il siluro e ogni tipo di cosa che poteva colpire, e oh simulare la ricorsione per fare connettività a 8 vie su catene di novae dal silurare una stella che era accanto ad altre stelle.

    
risposta data 28.07.2014 - 20:26
fonte
4

Mi limiterò a inserire un'altra citazione.

Programs must be written for people to read, and only incidentally for machines to execute

-- Harold Abelson

È molto improbabile che le funzioni che crescono a 100-200 seguano questa regola

    
risposta data 20.06.2014 - 14:17
fonte
1

Non ci sono regole assolute sulla lunghezza del metodo, ma le seguenti regole sono state utili:

  1. Lo scopo principale della funzione è trovare il valore restituito. Non c'è altra ragione per la sua esistenza. Una volta che il motivo è stato riempito, nessun altro codice dovrebbe essere inserito in esso. Ciò mantiene necessariamente le funzioni piccole. Chiamare altre funzioni dovrebbe essere fatto solo se rende più facile trovare il valore di ritorno.
  2. D'altra parte, le interfacce dovrebbero essere piccole. Ciò significa che hai un gran numero di classi, o hai grandi funzioni - una delle due succederà quando avrai iniziato ad avere abbastanza codice per fare qualcosa di significativo. Grandi programmi possono avere entrambi.
risposta data 05.02.2012 - 17:15
fonte
1

IMHO, non dovresti usare la barra di scorrimento per leggere la tua funzione. Non appena hai bisogno di spostare la barra di scorrimento, ci vuole un po 'di tempo per capire come funziona la funzione.

Di conseguenza, dipende dal solito ambiente di programmazione del lavoro di squadra (risoluzione dello schermo, editor, dimensione del carattere, ecc ...). Negli anni '80 erano 25 linee e 80 colonne. Ora, nel mio editor, visualizzo quasi 50 righe. Il numero di colonne che mostro non è cambiato da quando ho diviso il mio schermo in due per visualizzare due file a volte.

In breve, dipende dalla configurazione dei tuoi colleghi.

    
risposta data 05.07.2013 - 10:36
fonte
1

Gli autori significano la stessa cosa per "funzione" e "routine"? Tipicamente quando dico "funzione" intendo una subroutine / operazione che restituisce un valore e una "procedura" per una che non lo fa (e la cui chiamata diventa una singola istruzione). Questa non è una distinzione comune in tutto SE nel mondo reale, ma l'ho vista nei testi degli utenti.

In ogni caso, non c'è una risposta giusta a questo. Preferire l'uno o l'altro (se c'è una preferenza) è qualcosa che mi aspetterei di essere molto diverso tra lingue, progetti e organizzazioni; così come lo è con tutte le convenzioni di codice.

L'unica cosa che aggiungerei è che l'intera "operazione lunga non è più soggetta a errori delle operazioni brevi" l'asserzione non è propriamente vera. Oltre al fatto che più codice equivale a un maggior spazio di errore potenziale, è ovviamente ovvio che l'interruzione del codice in segmenti renderà gli errori più facili da evitare e più facili da individuare. Altrimenti non ci sarebbe alcuna ragione per rompere il codice in pezzi, salva la ripetizione. Ma questo è forse vero solo se i suddetti segmenti sono documentati abbastanza bene da poter determinare i risultati di una chiamata operativa senza leggere o tracciare il codice effettivo (design per contratto basato su specifiche piuttosto che una dipendenza concreta tra aree di codice).

Inoltre, se vuoi che le operazioni più lunghe funzionino correttamente, potresti adottare delle convenzioni più rigorose sul codice per supportarle. Lanciare una dichiarazione di ritorno nel mezzo di un'operazione potrebbe andar bene per una breve operazione, ma in operazioni più lunghe può creare una grande sezione di codice che è condizionale ma non ovviamente condizionale su una lettura rapida (solo per un esempio).

Quindi penserei che quale stile abbia meno probabilità di essere un incubo pieno di bug dipenda in gran parte da quali convenzioni si rispettano per il resto del codice. :)

    
risposta data 28.07.2014 - 22:48
fonte
1

Penso che la risposta di TomTom si sia avvicinata a quello che penso.

Sempre più mi trovo ad affrontare la complessità ciclomatica piuttosto che le linee.

Normalmente non scopo più di una struttura di controllo per metodo, ad eccezione di molti cicli necessari per gestire un array multidimensionale.

A volte mi trovo a mettere un if di una sola riga nei casi di switch perché, per qualche ragione, questi tendono ad essere casi in cui dividerlo ostacola piuttosto che aiutare.

Nota che non conto la logica di guardia contro questo limite.

    
risposta data 29.07.2014 - 06:42
fonte
-2

In OOP tutte le cose si oppongono e ci sono queste caratteristiche:

  1. Il polimorfismo
  2. Astrazione
  3. Inheritance

Quando osservi queste regole, i tuoi metodi sono generalmente piccoli ma non esistono regole per le regole piccole o molto piccole (ad esempio 2-3 linee). Un vantaggio del metodo piccolo (unità piccola, ad es. Metodo o funzione) sono:

  1. meglio leggibile
  2. mantieni meglio
  3. corretto bug
  4. cambia meglio
risposta data 05.02.2012 - 11:45
fonte

Leggi altre domande sui tag