I metodi lunghi sono sempre cattivi?

64

Quindi, guardandomi in giro, ho notato alcuni commenti sui metodi lunghi come cattiva pratica.

Non sono sicuro di essere sempre d'accordo sul fatto che i metodi lunghi siano cattivi (e vorrebbero le opinioni degli altri).

Ad esempio, ho alcune viste Django che eseguono un po 'di elaborazione degli oggetti prima di inviarli alla vista, un lungo metodo costituito da 350 linee di codice. Ho scritto il mio codice in modo che si occupi dei parametri - ordinando / filtrando il queryset, poi a mano a mano fa qualche elaborazione sugli oggetti che la mia query ha restituito.

Quindi l'elaborazione è principalmente un'aggregazione condizionale, che ha regole abbastanza complesse che non può essere facilmente eseguita nel database, quindi ho alcune variabili dichiarate al di fuori del ciclo principale e quindi vengono alterate durante il ciclo.

variable_1 = 0
variable_2 = 0
for object in queryset :
     if object.condition_condition_a and variable_2 > 0 :
     variable 1+= 1
     .....
     ...    
     . 
      more conditions to alter the variables

return queryset, and context 

Quindi secondo la teoria dovrei calcolare tutto il codice in metodi più piccoli, così che ho il metodo di visualizzazione come massimo di una pagina.

Tuttavia, avendo lavorato su varie basi di codice in passato, a volte trovo che rende il codice meno leggibile, quando è necessario passare costantemente da un metodo all'altro scoprendo tutte le parti di esso, mantenendo il metodo più esterno in la tua testa.

Trovo che avendo un lungo metodo ben formattato, puoi vedere la logica più facilmente, poiché non viene nascosto nei metodi interni.

Potrei calcolare il codice in metodi più piccoli, ma spesso c'è un ciclo interno usato per due o tre cose, quindi risulterebbe in un codice più complesso, o metodi che non fanno una cosa tranne due o tre (in alternativa potrei ripetere i loop interni per ogni compito, ma poi ci sarà un colpo di performance).

Quindi c'è un caso che i metodi lunghi non siano sempre cattivi? C'è sempre un motivo per scrivere metodi, quando saranno usati solo in un posto?

UPDATE: Sembra che abbia fatto questa domanda più di un anno fa.

Quindi ho rifattorizzato il codice dopo la risposta (mista) qui, divisa in metodi. È un'app di Django che recupera set complessi di oggetti correlati dal database, quindi l'argomento di test è fuori (probabilmente sarebbe stata necessaria la maggior parte dell'anno per creare oggetti rilevanti per i casi di test. ambiente di lavoro prima che qualcuno si lamenta). Correggere bug in quella parte del codice è leggermente più semplice ora, ma non in modo massiccio.

prima:

#comment 1 
bit of (uncomplicated) code 1a  
bit of code 2a

#comment 2 
bit of code 2a
bit of code 2b
bit of code 2c

#comment 3
bit of code 3

ora:

method_call_1
method_call_2
method_call_3

def method_1 
    bit of (uncomplicated) code 1a  
    bit of code 2a

def method_2 
    bit of code 2a
    bit of code 2b
    bit of code 2c

def method_3
    bit of code 3
    
posta wobbily_col 18.10.2013 - 20:37
fonte

18 risposte

78

No, i metodi lunghi non sono sempre male.

Nel libro Codice completo , si misura che i metodi lunghi sono a volte più veloci e più facili da scrivere e non portano a problemi di manutenzione.

In effetti, ciò che è veramente importante è rimanere ASCIUTTI e rispettare la separazione delle preoccupazioni. A volte, il calcolo è solo lungo da scrivere, ma in realtà non causerà problemi in futuro.

Tuttavia, dalla mia esperienza personale, la maggior parte dei metodi lunghi tende a mancare di separazione delle preoccupazioni. In effetti, i metodi lunghi sono un modo semplice per rilevare che qualcosa potrebbe essere sbagliato nel codice e che è necessario prestare particolare attenzione quando si esegue la revisione del codice.

EDIT: Mentre i commenti sono fatti, aggiungo un punto interessante alla risposta. In effetti, verificherei anche le metriche di complessità per la funzione (NPATH, complessità ciclomatica o ancora meglio CRAP).

In effetti, consiglio di non controllare tali parametri su funzioni lunghe, ma di includere avvisi su di essi con strumenti automatici (come checkstyle per Java per esempio) SU OGNI FUNZIONE.

    
risposta data 17.10.2012 - 23:33
fonte
54

Gran parte del focus qui sembra essere intorno alla parola always . Sì, gli assoluti sono cattivi, e l'ingegneria del software è tanto l'arte quanto la scienza, e tutto il resto ... ma dovrò dire che per l'esempio che hai dato, il metodo sarebbe meglio se fosse diviso su. Questi sono gli argomenti che in genere uso per giustificare la suddivisione del metodo:

Leggibilità: non sono sicuro degli altri, ma non riesco a leggere rapidamente 350 righe di codice. Sì, se è il mio proprio codice, e posso fare molte ipotesi al riguardo, potrei sfogliarlo molto rapidamente, ma questo è oltre il punto. Considera quanto sarebbe più semplice leggere quel metodo, se consisteva di 10 chiamate ad altri metodi (ognuno con un nome descrittivo). Facendo questo, hai introdotto una stratificazione nel codice, e il metodo di alto livello dà al lettore una breve e dolce descrizione di cosa sta succedendo.

Modifica - per metterlo in una luce diversa, pensaci in questo modo: come spiegheresti questo metodo a un nuovo membro del team? Sicuramente ha una struttura alcuni che puoi riassumere seguendo le linee di "bene, inizia facendo A, poi B, poi a volte C, ecc.". Avere un breve metodo di "panoramica" che chiama altri metodi rende evidente questa struttura. È estremamente raro trovare 350 righe di codice che non ne traggono beneficio; il cervello umano non è pensato per trattare elenchi di articoli di centinaia di elementi, li raggruppiamo.

Riutilizzabilità: i metodi lunghi tendono ad avere una bassa coesione; spesso fanno più di una cosa. La bassa coesione è il nemico del riuso; se unisci più compiti in un unico metodo, finirà per essere riutilizzato in meno punti di quello che avrebbe dovuto essere.

Testabilità e coesione: ho menzionato complessità ciclomatica in un commento sopra: è un bel buona misura di quanto sia complesso il tuo metodo. Rappresenta il limite inferiore del numero di percorsi univoci attraverso il codice, a seconda degli input (modifica: corretta come da commento di MichaelT). Significa anche che per testare correttamente il metodo, è necessario disporre di almeno un numero di casi di test del numero di complessità ciclomatica. Sfortunatamente, quando metti insieme pezzi di codice che non sono realmente dipendenti l'uno dall'altro, non c'è modo di essere sicuri di questa mancanza di dipendenza e la complessità tende a moltiplicarsi. Puoi pensare a questa misura come un'indicazione del numero di cose diverse che stai cercando di fare. Se è troppo alto, è il momento di dividere e conquistare.

Refactoring e struttura: I metodi lunghi sono spesso segni di mancanza di una struttura nel codice. Spesso, lo sviluppatore non è riuscito a capire quali sono le caratteristiche comuni tra le diverse parti di quel metodo e dove è possibile tracciare una linea tra di esse. Rendendosi conto che un lungo metodo è un problema, e il tentativo di dividerlo in metodi più piccoli è il primo passo su una strada più lunga per identificare effettivamente una struttura migliore per l'intera faccenda. Forse hai bisogno di creare una classe o due; alla fine non sarà necessariamente più complesso!

Penso anche che in questo caso la scusa per avere un metodo lungo è "... alcune variabili dichiarate al di fuori del ciclo principale vengono alterate durante il ciclo". Non sono un esperto di Python, ma sono abbastanza certo che questo problema potrebbe essere banalmente risolto da qualche forma di passaggio per riferimento.

    
risposta data 15.10.2012 - 16:58
fonte
28

I metodi lunghi sono sempre cattivi, ma a volte sono meglio delle alternative.

    
risposta data 15.10.2012 - 13:39
fonte
8

I metodi lunghi sono un codice olfattivo . Di solito indicano che qualcosa non va, ma non è una regola dura e veloce. Di solito i casi in cui sono giustificati coinvolgono un sacco di regole di business statali e abbastanza complesse (come hai trovato).

Per quanto riguarda l'altra domanda, è spesso utile separare blocchi di logica in metodi separati, anche se vengono chiamati solo una volta. Rende più facile vedere la logica di alto livello e può rendere la gestione delle eccezioni un po 'più pulita. Basta che non sia necessario passare venti parametri per rappresentare lo stato di elaborazione!

    
risposta data 15.10.2012 - 14:11
fonte
7

I metodi lunghi non sono sempre male. Di solito sono un segno che potrebbe esserci un problema.

Sul sistema su cui sto lavorando abbiamo una mezza dozzina di metodi che sono più di 10.000 righe. Uno è attualmente lungo 5430 linee. Ed è OK.

Queste funzioni ridicolmente lunghe sono molto semplici e sono autogenerate. Quel grande mostro lungo la linea 5530 contiene i dati di movimento polare giornalieri dal 1 gennaio 1962 al 10 gennaio 2012 (la nostra ultima versione). Rilasciamo anche una procedura con cui i nostri utenti possono aggiornare il file generato automaticamente. Tale file sorgente contiene i dati di movimento polare dal link , auto-convertiti in C ++ .

Leggere il sito web al volo non è possibile in un'installazione sicura. Non c'è connessione con il mondo esterno. Anche il download del sito Web come copia locale e l'analisi in C ++ non sono un'opzione; l'analisi è lenta e deve essere veloce. Download, auto-traduzione in C ++ e compilazione: ora hai qualcosa che è veloce. (Basta non compilarlo ottimizzato È incredibile quanto tempo impiega un compilatore ottimizzante per compilare 50.000 linee di codice in linea estremamente semplice. Ci vuole più di mezz'ora sul mio computer per compilare quel file ottimizzato e l'ottimizzazione non fa assolutamente nulla Non c'è niente da ottimizzare: è un semplice codice di linea, una dichiarazione di assegnazione dopo l'altra.)

    
risposta data 15.10.2012 - 14:32
fonte
7

Diciamo solo che ci sono modi buoni e cattivi per rompere un metodo lungo. Dovendo "[mantenere] il metodo più esterno nella tua testa" è un segno che non lo stai distruggendo nel modo più ottimale, o che i tuoi submetodi sono poco nominati. In teoria, ci sono casi in cui un metodo lungo è migliore. In pratica, è estremamente raro. Se non riesci a capire come rendere leggibile un metodo più breve, chiedi a qualcuno di rivedere il tuo codice e chiedi loro in particolare le idee su come abbreviare i metodi.

Come per i loop multipli che causano un presunto colpo di performance, non c'è modo di saperlo senza misurare. Più loop più piccoli possono essere significativamente più veloci se significano che tutto ciò di cui ha bisogno può rimanere nella cache. Anche se si verifica un calo di prestazioni, di solito è trascurabile a favore della leggibilità.

Dirò che spesso i metodi lunghi sono più facili da scrivere , anche se sono più difficili da leggere . Ecco perché proliferano anche se a nessuno piacciono. Non c'è niente di sbagliato nella pianificazione dall'inizio del refactoring prima di registrarlo.

    
risposta data 15.10.2012 - 15:58
fonte
4

I metodi lunghi possono essere più computazionali e efficienti nello spazio, può essere più semplice vedere la logica e semplificarne il debug. Tuttavia, queste regole si applicano solo quando un solo programmatore tocca quel codice. Il codice sarà un problema da estendere se non è atomico, essenzialmente la prossima persona dovrà ricominciare da zero e quindi eseguire il debug e test ciò richiederà per sempre dal momento che non utilizza alcun codice valido noto.

    
risposta data 15.10.2012 - 14:20
fonte
3

C'è qualcosa che chiamiamo Decomposizione funzionale che implica l'interruzione dei metodi più lunghi in quelli più piccoli laddove possibile. Come hai detto che il tuo metodo implica l'ordinamento / il filtro, allora è meglio avere metodi o funzioni separati per queste attività.

Precisamente, il tuo metodo dovrebbe essere focalizzato solo su perforimng 1 task.

E se ha bisogno di chiamare un altro metodo per qualche ragione, allora fallo altrimenti con quello che stai già scrivendo. Inoltre, per i prereg di leggibilità, è possibile aggiungere commenti. Convenzionalmente, i programmatori usano commenti a più righe (/ ** / in C, C ++, C # e Java) per le descrizioni dei metodi e usano commenti a riga singola (// in C, C ++, C # e Java). Sono disponibili anche buoni strumenti di documentazione per una maggiore leggibilità del codice (ad esempio JavaDoc ). Puoi anche consultare commenti basati su XML se sei uno sviluppatore .Net.

I loop influiscono sulle prestazioni del programma e potrebbero causare un sovraccarico dell'applicazione se non utilizzati correttamente. L'idea è di progettare il tuo algoritmo in modo tale che tu usi i cicli annidati il meno possibile.

    
risposta data 15.10.2012 - 14:36
fonte
3

È perfettamente giusto scrivere funzioni lunghe. Ma varia nel contesto se hai davvero bisogno o meno. Ad es. alcuni dei migliori algoritmi sono espressi meglio quando si tratta di un peice. D'altra parte, una grande percentuale di routine in programmi orientati agli oggetti saranno routine accessorie, che saranno molto brevi. Alcune delle lunghe procedure di elaborazione che hanno casi di commutazione lunghi, se le condizioni possono essere ottimizzate tramite metodi basati su tabelle.

C'è una breve discussione eccellente in Code Complete 2 sulla lunghezza delle routine.

The theoretical best 497 maximum length is often described as one or two pages of program listing, 66 to 132 lines. Modern programs tend to have volumes of extremely short routines mixed in with a few longer routines.

Decades of evidence say that routines of such length are no more error prone than shorter routines. Let issues such as depth of nesting, number of variables, and other complexity-related considerations dictate 535 the length of the routine rather than imposing a length

If you want to write routines longer than about 200 lines, be careful. None of the studies that reported decreased cost, decreased error rates, or both with larger routines distinguished among sizes larger than 200 lines, and you’re bound to run into an upper limit of understandability as you pass 200 lines of code. 536 restriction per se.

    
risposta data 16.10.2012 - 08:12
fonte
2

Un altro voto è quasi sempre sbagliato. Trovo due casi di base in cui è la risposta corretta, però:

1) Un metodo che in pratica chiama solo una serie di altri metodi e non ha alcun lavoro vero. Hai un processo che richiede 50 passaggi per ottenere, si ottiene un metodo con 50 chiamate in esso. Di solito non c'è nulla da guadagnare cercando di rompere questo.

2) Dispatcher. La progettazione OOP si è liberata di molti di questi metodi, ma le fonti di dati in arrivo sono per loro natura solo dati e quindi non possono seguire i principi OOP. Non è esattamente insolito avere una sorta di routine di dispatcher nel codice che gestisce i dati.

Direi anche che non si dovrebbe nemmeno prendere in considerazione la questione quando si tratta di cose generate automaticamente. Nessuno sta cercando di capire che cosa fa il codice generato automaticamente, non importa una virgola se è facile da capire per un essere umano.

    
risposta data 16.10.2012 - 00:52
fonte
2

Volevo rispondere all'esempio che hai dato:

For example I have some Django views that do a bit of processing of the objects before sending them to the view, a long method being 350 lines of code. I have my code written so that it deals with the paramaters - sorting / filtering the queryset, then bit by bit does some processing on the objects my query has returned.

Nella mia azienda il nostro progetto più grande è costruito su Django e abbiamo anche funzioni a vista lunga (molte sono più di 350 linee). Direi che i nostri non devono essere così lunghi e ci stanno facendo del male.

Queste funzioni di visualizzazione stanno svolgendo molti lavori vagamente correlati che dovrebbero essere estratti nel modello, nelle classi helper o nelle funzioni di supporto. Inoltre, finiamo per riutilizzare le visualizzazioni per fare cose diverse, che dovrebbero invece essere divise in viste più coerenti.

Sospetto che le tue opinioni abbiano caratteristiche simili. Nel mio caso, so che causa problemi e sto lavorando per apportare modifiche. Se non sei d'accordo sul fatto che sta causando problemi, non devi risolverlo.

    
risposta data 17.10.2012 - 20:37
fonte
2

Non so se qualcuno lo abbia già menzionato, ma uno dei motivi per cui i metodi lunghi sono cattivi è che di solito coinvolgono diversi livelli di astrazione. Hai variabili di loop e accadono tutti i tipi di cose. Considera la funzione fittizia:

function nextSlide() {
  var content = getNextSlideContent();
  hideCurrentSlide();
  var newSlide = createSlide(content);
  setNextSlide(newSlide);
  showNextSlide();
}

Se si facesse tutta l'animazione, il calcolo, l'accesso ai dati ecc. in quella funzione sarebbe stato un disastro. La funzione nextSlide () mantiene un livello di astrazione coerente (il sistema dello stato della diapositiva) e ignora gli altri. Questo rende il codice leggibile.

Se devi costantemente passare a metodi più piccoli per vedere cosa fanno, l'esercizio della divisione della funzione è fallito. Solo perché il codice che stai leggendo non sta facendo cose ovvie nei metodi figli non significa che i metodi figli siano una cattiva idea, solo che è stato fatto in modo errato.

Quando creo metodi, di solito finisco per dividerli in metodi più piccoli come una sorta di strategia di divisione e conquista. Un metodo come

   if (hasMoreRecords()) { ... }

è sicuramente più leggibile di

if (file.isOpen() && i < recordLimit && currentRecord != null) { ... } 

Giusto?

Sono d'accordo sul fatto che le affermazioni assolute siano cattive e che sia anche un metodo lungo male.

    
risposta data 14.11.2012 - 08:28
fonte
1

La vera storia. Una volta ho incontrato un metodo lungo oltre duemila righe. Il metodo aveva regioni che descrivevano ciò che stava facendo in quelle regioni. Dopo aver letto attraverso una regione, ho deciso di fare un metodo di estrazione automatizzata, nominandolo in base al nome della regione. quando ho finito, il metodo non era altro che 40 chiamate al metodo di circa cinquanta righe ciascuna e tutto ha funzionato allo stesso modo.

Ciò che è troppo grande è soggettivo. A volte, un metodo non può essere scomposto oltre quello che è attualmente. È come scrivere un libro. La maggior parte delle persone concorda sul fatto che i paragrafi lunghi dovrebbero di solito essere divisi. Ma a volte, c'è solo un'idea e la sua divisione causa più confusione di quella che sarebbe causata dalla lunghezza di quel paragrafo.

    
risposta data 15.10.2012 - 20:12
fonte
0

Il punto di un metodo è di aiutare a ridurre il rigurgito del codice. Un metodo dovrebbe avere una funzione specifica di cui è responsabile. Se si finisce con il codice di rilocalizzazione in numerosi punti, si corre il rischio che il codice abbia risultati imprevisti se le specifiche del software sono progettate per essere cambiate.

Affinché un metodo abbia 350 righe suggerirebbe che molte delle attività che sta eseguendo vengono replicate altrove poiché è inusuale richiedere una quantità così grande di codice per svolgere un'attività specializzata.

    
risposta data 15.10.2012 - 13:35
fonte
0

Non sono proprio i metodi lunghi che sono cattivi, ma li lascia di più.

Intendo che l'atto effettivo di refactoring del tuo campione da:

varaible_1 = 0
variable_2 = 0
for object in queryset :
     if object.condition_condition_a and variable_2 > 0 :
     variable 1+= 1
     .....
     ...    
     . 
      more conditions to alter the variables

return queryset, and context 

a

Status status = new Status();
status.variable1 = 0;
status.variable2 = 0;
for object in queryset :
     if object.condition_condition_a and status.variable2 > 0 :
     status.variable1 += 1
     .....
     ...    
     . 
      more conditions to alter the variables (status)

return queryset, and context 

e poi a

class Status {
    variable1 = 0;
    variable2 = 0;

    void update(object) {
        if object.condition_condition_a and variable2 > 0 {
            variable1 += 1
        }
    }
};

Status status = new Status();
for object in queryset :
     status.update(object);
     .....
     ...    
     . 
      more conditions to alter the variables (status)

return queryset, and context 

ora sei sulla buona strada non solo per un metodo molto più breve ma molto più utilizzabile e comprensibile.

    
risposta data 15.10.2012 - 16:28
fonte
0

Penso che il fatto che il metodo sia molto lungo è qualcosa da controllare, ma sicuramente non è un anti-pattern istantaneo. La grande cosa da cercare in enormi metodi è un sacco di nidificazione. Se hai

foreach(var x in y)
{
    //do ALL the things
    //....
}

e il corpo del loop non è estremamente localizzato (cioè potresti inviarlo a meno di 4 parametri), quindi probabilmente è meglio convertirlo in:

foreach(var x in y)
{
    DoAllTheThings(x);
}
...
void DoAllTheThings(object x)
{
    //do ALL the things
    //....
}

A sua volta, questo può ridurre notevolmente la lunghezza di una funzione. Inoltre, assicurati di cercare il codice duplicato nella funzione e spostalo in una funzione separata

Infine, alcuni metodi sono solo lunghi e complicati e non c'è nulla che tu possa fare. Alcuni problemi richiedono soluzioni che non si prestino facilmente al codice. Ad esempio, l'analisi di una grammatica di molta complessità può rendere metodi davvero lunghi di cui davvero non si può fare a meno senza peggiorare la situazione.

    
risposta data 15.10.2012 - 17:59
fonte
0

La verità è, dipende. Come accennato, se il codice non separa le preoccupazioni e cerca di fare tutto in un unico metodo, allora è un problema. La separazione del codice in più moduli semplifica la lettura del codice e la scrittura del codice (da parte di più programmatori). Aderire a un modulo (classe) per file sorgente è una buona idea per cominciare.

In secondo luogo, quando si tratta di funzioni / procedure:

void setDataValueAndCheckForRange(Data *data) {/*code*/} 

è un buon metodo se controlla l'intervallo di soli "Dati". È un metodo BAD quando lo stesso intervallo si applica a più funzioni (esempio di codice errato):

void setDataValueAndCheckForRange(Data *data){ /*code */}
void addDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void subDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void mulDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}

Questo deve essere rifattorizzato per:

bool isWithinRange(Data *d){ /*code*/ }
void setDataValue(Data *d) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void addDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void subDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void mulDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 

Codice di REUSE il più possibile. E questo è possibile quando ogni funzione del tuo programma è SEMPLICE (non necessariamente facile) abbastanza.

PREVENTIVO: Il MOUNTAIN è composto da piccoli granelli di terra. L'oceano è costituito da piccole gocce d'acqua .. (- Sivananda)

    
risposta data 16.10.2012 - 13:41
fonte
0

I metodi lunghi tendono a "cattivi" in linguaggi imperativi che favoriscono affermazioni, effetti collaterali e mutabilità, proprio perché queste caratteristiche aumentano la complessità e quindi i bug.

Nei linguaggi di programmazione funzionale, che favoriscono espressioni, purezza e immutabilità, c'è meno motivo di preoccupazione.

Sia nei linguaggi funzionali che in quelli imperativi, è sempre meglio scomporre parti di codice riutilizzabili in comuni routine di primo livello, ma nei linguaggi funzionali che supportano lo scope lessicale con funzioni nidificate ecc., è in realtà un incapsulamento migliore per nascondere le sub-routine all'interno della funzione di livello superiore (metodo) piuttosto che suddividerle in altre funzioni di primo livello.

    
risposta data 16.10.2012 - 19:29
fonte

Leggi altre domande sui tag