Perché le persone sono così strongmente contrarie ai tag #region nei metodi?

27

Ho sentito parlare molto di come mantenere i metodi brevi e ho sentito molti programmatori dire che l'uso dei tag #region all'interno di un metodo è un segnale sicuro che è troppo lungo e dovrebbe essere rifatto in più modi. Tuttavia, mi sembra che ci siano molti casi in cui la separazione del codice con i tag #region all'interno di un metodo è la soluzione superiore per il refactoring in più metodi.

Supponiamo di avere un metodo il cui calcolo può essere separato in tre fasi piuttosto distinte. Inoltre, ognuna di queste fasi è rilevante solo per il calcolo del metodo questo , e quindi l'estrazione in nuovi metodi non ci consente di riutilizzare il codice. Quali sono, allora, i benefici dell'estrazione di ogni fase nel proprio metodo? Per quanto ne so, tutto ciò che otteniamo è una certa leggibilità e uno scope variabile separato per ciascuna fase (che aiuterà a evitare che le modifiche di una particolare fase possano rompere accidentalmente un'altra fase).

Tuttavia, entrambi possono essere raggiunti senza estrarre ciascuna fase nel proprio metodo. I tag Region ci permettono di comprimere il codice in una forma che sia leggibile (con l'ulteriore vantaggio che non dobbiamo più lasciare il nostro posto in questo file se decidessimo di espandere ed esaminare il codice), e semplicemente avvolgendo ogni fase in {} crea il proprio ambito con cui lavorare.

Il vantaggio di farlo in questo modo è che non inquiniamo lo scope di livello di classe con tre metodi che sono in realtà rilevanti solo per il funzionamento interno di un quarto metodo. Il refactoring immediato di un metodo lungo in una serie di metodi brevi mi sembra il riutilizzo del codice equivalente all'ottimizzazione prematura; state introducendo una complessità extra per affrontare un problema che in molti casi non si pone mai. È sempre possibile estrarre una delle fasi in un proprio metodo in un secondo momento, qualora si presentasse l'opportunità di riutilizzare il codice.

Pensieri?

    
posta jjoelson 09.11.2011 - 18:47
fonte

9 risposte

64

Tutto quello che dovresti mai preoccuparti è che il tuo codice sia utilizzabile, non riutilizzabile. Una scimmia può trasformare il codice utilizzabile in codice riutilizzabile, se ci sono delle trasformazioni da fare affatto.

L'argomento "Ho bisogno di questo solo qui" è povero, per dirla in modo educato. La tecnica che stai descrivendo è spesso definita come la tecnica dei titoli ed è generalmente disapprovata.

  1. Non puoi testare le regioni, ma puoi testare i veri metodi in isolamento.
  2. Le regioni sono commenti, non elementi sintattici. Nel peggiore dei casi il nidificazione delle tue regioni e i tuoi blocchi si contraddicono a vicenda. Dovresti sempre cercare di rappresentare la semantica della tua struttura con gli elementi sintattici della lingua che stai utilizzando.
  3. Dopo aver rifatto i metodi, uno non ha più bisogno di essere piegato per leggere il testo. Uno potrebbe guardare il codice sorgente in qualsiasi strumento che non abbia pieghe, come un terminale, un client di posta, una visualizzazione web per il tuo VCS o un diff-viewer.
  4. Dopo aver effettuato il refactoring sui metodi, il metodo risultante è almeno buono come con le regioni, inoltre è più semplice spostare le singole parti intorno.
  5. Il principio di responsabilità singola suggerisce che ogni unità dovrebbe avere solo un'attività e una sola attività. Il compito del metodo "principale" è quello di comporre i metodi "helper" per ottenere il risultato desiderato. I metodi "helper" risolvono un problema semplice e discreto in isolamento. La classe che contiene tutti questi metodi dovrebbe inoltre soddisfare solo un'attività e contenere solo i metodi relativi a tale attività. Quindi i metodi helper appartengono all'ambito della classe, o il codice non dovrebbe essere nella classe in primo luogo, ma almeno in qualche metodo globale o, meglio ancora, dovrebbe essere iniettato .

Rilevante anche: Le opinioni di Jeff Atwoods sulla piegatura del codice

    
risposta data 09.11.2011 - 19:30
fonte
15

Non sono le regioni nei metodi a rappresentare il problema, ma il caso in cui è necessario (o persino utile) inserire le regioni nei metodi.

Avendo dovuto eseguire il debug di molti dei 200-300 metodi di linea, posso dirti che non lo augurerei a nessuno. Se stai scrivendo e stai attento al tuo codice, va bene, fai tutto quello che vuoi. Ma una volta che qualcuno lo guarda, dovrebbe essere immediatamente chiaro che cosa sta facendo un metodo.

"First it gets the things, and then it jigs them around, then if they need buzzing it does that, otherwise it checks whether they are blue and inverts their Copernicus value. Then, if the second thing is greater than the first thing, we need to get the juice box and insert it... "

No, non è abbastanza buono. Rompila nelle regioni tutto ciò che vuoi ma sto ancora scuotendo la testa solo a pensarci.

Se esplori i metodi ottieni molti vantaggi:

  • Comprensione più chiara di ciò che sta accadendo dove, anche se solo attraverso il nome del metodo. E sei sbagliato che un metodo non può essere completamente documentato - aggiungi il blocco della documentazione (hit /// ) e inserisci la descrizione, i parametri, il tipo di ritorno e anche exception , example , remarks nodi per dettagliare tali scenari. Ecco dove va, ecco a cosa serve!
  • Non ci sono effetti collaterali o problemi di scoping. Potresti dire che dichiari tutte le tue variabili in un sotto-campo con {} , ma devo verificare ogni metodo per assicurarmi di non "imbrogliare"? Questo dodgyObject sto usando qui solo perché usato in questa regione, o è venuto da qualche altra parte? Se fossi nel mio stesso metodo sarei facilmente in grado di vedere se è stato passato a me o se l'ho creato io stesso (o meglio, se lo hai creato).
  • Il mio log degli eventi mi dice quale metodo si è verificata un'eccezione. Sì, potrei riuscire a trovare il codice incriminato con un numero di riga, ma conoscere il nome del metodo (specialmente se li ho nominati correttamente) mi dà molto buona indicazione di cosa è successo e cosa è andato storto. "Oh, il metodo TurnThingsUpsideDown fallito - probabilmente fallire mentre si gira una cosa brutta" è molto meglio di "Oh, il metodo DoEverything fallito - potrebbe essere stato 50 cose diverse e dovrò scavare per un'ora per trovarlo ".

Questo è tutto prima di ogni preoccupazione di riusabilità o improvvisazione. I metodi correttamente separati facilitano ovviamente il riutilizzo e consentono anche di sostituire facilmente un metodo che non funziona (troppo lento, troppo bacato, dipendenza modificata ecc.). Hai mai provato a refactoring uno di questi metodi enormi? Non c'è modo di sapere che ciò che stai cambiando non avrà effetto su nessun altro.

Per favore incapsula correttamente la tua logica in metodi di dimensioni ragionevoli. So che è facile per loro sfuggire di mano, e sostenere che non vale la pena ridisegnare una volta a quel punto è un altro problema. Ma devi accettare il fatto che non c'è alcun danno e almeno potenziale beneficio nell'avere metodi incapsulati, scritti in modo pulito e semplicemente progettati.

    
risposta data 09.11.2011 - 23:34
fonte
6

Se il tuo metodo è abbastanza complesso da poter essere separato in tre fasi distinte, allora non solo dovrebbero essere tre metodi separati ... ma il tuo metodo dovrebbe essere una classe separata, dedicata a quel calcolo.

"Ma allora la mia classe avrà un solo metodo pubblico!"

Hai ragione, e non c'è niente di sbagliato in questo. L'idea del principio della responsabilità unica è che la tua classe fa una cosa .

La tua classe può chiamare a turno ciascuno dei tre metodi e restituire il risultato. Una cosa.

Come bonus, ora il tuo metodo è testabile perché non è più un metodo privato.

Ma non preoccuparti di una classe; in realtà dovresti avere four : uno per ciascuno dei pezzi del tuo metodo, più uno che prende ciascuno dei tre come dipendenza e li chiama nell'ordine corretto, restituendo il risultato.

Questo rende le tue classi persino più testabili. Ti permette anche di cambiare facilmente uno di quei tre pezzi. Basta scrivere una nuova classe ereditando da quella vecchia e ignorare il comportamento modificato. Oppure crea un'interfaccia per il comportamento di ciascuno dei tuoi tre pezzi; e quindi per sostituirne uno, basta scrivere una nuova classe che implementa quell'interfaccia e sostituirla con quella vecchia nella configurazione del contenitore di iniezione delle dipendenze.

Che cosa? Non stai usando l'iniezione di dipendenza? Bene, probabilmente non hai ancora visto il bisogno, perché non stai seguendo il principio della responsabilità unica. Una volta avviato, scoprirai che il modo più semplice per assegnare a ciascuna classe le sue dipendenze è utilizzare un contenitore DI.

Modifica :

OK, accantoniamo la domanda di refactoring. Lo scopo di #region è quello di nascondere il codice , che significa principalmente codice che non ha bisogno di essere modificato in circostanze normali. Il codice generato è il miglior esempio. Dovrebbe essere nascosto nelle regioni perché di solito non è necessario modificarlo. Se è necessario, l'atto di espandere una regione ti dà un po 'di avviso "Here be dragons" per assicurarti di sapere cosa stai facendo.

Quindi direi che #region dovrebbe non essere usato nel mezzo di un metodo. Se apro un metodo, voglio vedere il codice. L'uso di #region mi dà un altro livello di nascondimento che non mi serve, e questo diventa un fastidio: spiego il metodo ... e ancora non riesco a vedere alcun codice.

Se sei preoccupato per le persone che vedono la struttura del metodo (e rifiuti di refactarlo), quindi aggiungi i commenti del banner come questo:

//
// ---------- COMPUTE SOMETHING OR OTHER ----------
//

Ciò consentirà a chi naviga nel codice di visualizzare le singole parti del tuo metodo senza dover affrontare l'espansione e il collasso dei tag #region .

    
risposta data 09.11.2011 - 20:43
fonte
4

Penso che l'esempio che dai alla tua discussione sia meno su YAGNI (non ne avrai bisogno) e altro sulla scrittura di un codice di qualità adeguato che sia facilmente mantenibile.

Nei primi corsi di programmazione apprendiamo che se un metodo è lungo 700 LOC, probabilmente è troppo grande e sicuramente sarebbe un enorme problema provare e eseguire il debug.

In secondo luogo se scrivo i metodi A, B e C che sono usati solo nel metodo D, allora posso più facilmente testare l'unità A B e C indipendentemente da D, consentendo test unitari più robusti in tutti e 4 i metodi.

    
risposta data 09.11.2011 - 18:55
fonte
3

Suppose we have a method whose computation can be separated into three rather distinct phases. Furthermore, each of these stages is only relevant to the computation for this method, and so extracting them into new methods gains us no code reuse. What, then, are the benefits of extracting each phase into it's own method? As far as I can tell, all we gain is some readability and a separate variable scope for each phase (which will help prevent modifications of a particular phase from accidentally breaking another phase).

Questi sono due vantaggi. Ma quello importante, per me, è la debuggability . Se devi tracciare il codice, è molto più semplice riuscire a scavalcare due delle tre sezioni e tracciare solo quello che ti interessa. Il refactoring in metodi più piccoli lo consente; i tag delle regioni no.

    
risposta data 09.11.2011 - 19:22
fonte
2

La mia regola personale è che se un metodo è più lungo di uno schermo, diciamo 40 righe, è troppo lungo. Se una classe è più lunga di 500 linee, è troppo grande. Rompendo queste regole a volte, occasionalmente anche da un grande multiplo, ma di solito mi rammarico entro l'anno.

Anche un metodo da 20 a 30 linee sta diventando un po 'lungo nella maggior parte dei casi. Quindi direi che usare le regioni in un metodo di solito è una cattiva idea, semplicemente perché non avrebbe quasi mai senso comprimere una regione di 20 o 30 linee in più sottoregioni.

L'interruzione di funzioni lunghe per questa definizione ha almeno due vantaggi:

  1. Si arriva a mettere un nome su ciò che queste sub-funzioni stanno facendo, il che chiarisce il pensiero presente e semplifica il compito dei manutentori successivi.

  2. La scomposizione in funzioni più piccole obbliga a passare solo i parametri di cui hanno bisogno le funzioni più piccole, quindi l'ambito dei dati che richiedono e modifica è molto chiaro. Questo presuppone che tu non stia accedendo e modificando lo stato dell'oggetto in cento diverse funzioni foglia, che anch'io scoraggerei.

Nella mia esperienza, queste regole producono codice che è più facile da scrivere senza commettere errori comuni e può essere compreso e modificato in seguito senza rompere i comportamenti sottili. Non importa se la persona che deve capire / modificare il codice è qualcun altro, o me stesso dopo che ho dormito e ho dimenticato tutto.

Quindi considererei le regioni nei metodi un "odore di codice" che indica che vengono applicate pratiche insostenibili. Come sempre, potrebbe essere eccezioni, ma si verificano così raramente che quasi non vale la pena discuterne.

    
risposta data 09.11.2011 - 19:59
fonte
2

Qui andiamo di nuovo ... Ci sono un sacco di altri argomenti qui sui programmatori che sono finiti nella stessa discussione.

Indicate alcuni argomenti molto interessanti relativi alle funzioni brevi. Non è una dichiarazione popolare, ma sono con te su questo . Dopo averlo discusso spesso, è la mia impressione che la discussione di solito si riduce a se ti concentri principalmente sul corretto incapsulamento, o lo sviluppo guidato dai test al massimo. Questi sono i due principali contendenti in quello che sembra essere l'ennesima guerra santa, e temo di essere sotto il lato di potenza.

Tuttavia ...

La soluzione secondo me non è sicuramente quella di avvolgere le "fasi" nelle regioni. La piegatura del codice viene utilizzata per spazzare il codice sotto il tappeto. Invia il codice lì così com'è, potrebbe ricordarti che potresti volerlo refactoring in un secondo momento! Per metterlo in relazione con un argomento nella tua domanda: come sarai in grado di vedere un'opportunità di riutilizzo del codice se non vedi alcun codice? Lunghi segmenti di codice dovrebbero essere esattamente questo, una spina nell'occhio che ti fa venire voglia di refactarlo.

Come sostituto appropriato per le regioni, ti suggerisco di usare code paragraph come Alex Papadimoulis li chiama in un commento . In realtà potresti già utilizzare un approccio simile. Sono semplicemente blocchi di codice con un'intestazione di commento, che spiega cosa fa l'intero blocco. Hai la leggibilità delle funzioni, ma sei in grado di usare frasi inglesi normalmente distanziate, e non perdi alcun incapsulamento.

    
risposta data 10.11.2011 - 01:20
fonte
1

Anche se sono d'accordo che di solito è meglio refactoring del codice che usare #region , non è sempre possibile.

Per supportare questa affermazione, vorrei fare un esempio.

class Foo : IComparable
{
  private String name;
  private int foo;
  private Bar bar;

  public int CompareTo(Foo other)
  {
#region Ascending on name
     if (this.name < other.name) return -1;
     if (this.name > other.name) return 1;
#endregion
#region Usual order on bar
     int compBar = bar.compareTo(other.bar);
     if (compBar != 0) return compBar;
#endregion
#region Descending on foo
     if (this.foo > other.foo) return -1;
     if (this.foo < other.foo) return 1;
#endregion
     return 0; // equal
  }

È facile riadattare le regioni per modificare l'ordine o estendere quando i campi aggiuntivi vengono aggiunti alla classe. I commenti sono comunque necessari per vedere rapidamente come dovrebbe funzionare l'ordinamento. E soprattutto: non vedo come il refactoring aiuterà la leggibilità in questo caso.

Tuttavia, queste eccezioni sono rare. Di solito è possibile refactoring, come dire molte delle altre risposte.

    
risposta data 10.11.2011 - 22:47
fonte
0

Non penso che ci sia una risposta unica per il motivo per cui certe persone o gruppi decidono di non usare #region ma se dovessi elencarne alcuni sarebbero i principali motivi che ho visto.

  • I nomi e l'ordine delle regioni rendono più esplicito l'ordine e l'organizzazione del codice che applica uno stile particolare che può diventare una fonte di contesa e di bikehedding.
  • La maggior parte dei concetti non si inserisce perfettamente in un piccolo numero di regioni. In genere, inizi con le regioni in base ai modificatori di accesso public , private , quindi in base al tipo di membro (ad esempio metodo, proprietà, campo), ma a proposito dei costruttori che si estendono su quei modificatori, statici varietà di tutti questi elementi, membri protetti, membri interni, membri interni protetti, membri impliciti dell'interfaccia, eventi, ecc. Alla fine si avranno classi più ben progettate in cui si dispone di un singolo o di un metodo in ciascuna regione a causa della categorizzazione granulare.
  • Se sei in grado di mantenere le cose pragmatiche e solo raggruppate in un gruppo di tre o quattro tipi costanti di regioni, allora può funzionare, ma quale valore aggiunge davvero? Se stai progettando bene le classi, non dovresti davvero dover setacciare pagine di codice.
  • Come accennato sopra, le regioni possono nascondere il brutto codice che può far sembrare meno problematico di quello che è. Mantenerlo come una piaga per gli occhi può essere d'aiuto nel fargli fronte.
risposta data 23.03.2016 - 14:30
fonte

Leggi altre domande sui tag