Un odore di codice è un sintomo che indica che esiste un problema nella progettazione che potrebbe potenzialmente aumentare il numero di bug: questo non è il caso delle regioni, ma le regioni possono contribuire a creare odori di codice, come i metodi lunghi.
Da:
Un anti-pattern (o antipattern) è un pattern utilizzato in operazioni sociali o aziendali o ingegneria del software che può essere comunemente usato ma è inefficace e / o controproducente nella pratica
le regioni sono anti-schemi. Richiedono più lavoro che non aumenta la qualità o la leggibilità del codice, che non riduce il numero di bug e che può solo rendere il codice più complicato per refactoring.
Non usare le regioni all'interno dei metodi; refactor invece
I metodi devono essere brevi . Se ci sono solo dieci righe in un metodo, probabilmente non useresti le regioni per nasconderne cinque quando lavori su altre cinque.
Inoltre, ogni metodo deve fare una e una sola cosa . Le regioni, d'altra parte, hanno lo scopo di separare cose diverse . Se il tuo metodo fa A, quindi B, è logico creare due regioni, ma questo è un approccio sbagliato; invece, dovresti riformattare il metodo in due metodi separati.
L'uso delle regioni in questo caso può anche rendere più difficile il refactoring. Immagina di avere:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
Il collasso della prima regione per concentrarsi sulla seconda non è solo rischioso: possiamo facilmente dimenticare l'eccezione che interrompe il flusso (potrebbe esserci una clausola di guardia con un return
invece, che è ancora più difficile da individuare), ma avrebbe anche un problema se il codice deve essere refactored in questo modo:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Ora, le regioni non hanno senso e non è possibile leggere e comprendere il codice nella seconda regione senza guardare il codice nella prima.
Un altro caso che a volte vedo è questo:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
È allettante utilizzare le regioni quando la convalida degli argomenti inizia a decine di LOC, ma esiste un modo migliore per risolvere questo problema: quello utilizzato dal codice sorgente di .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
Non utilizzare le regioni al di fuori dei metodi per raggruppare
Alcune persone li usano per raggruppare campi, proprietà, ecc. Questo approccio è sbagliato: se il codice è conforme a StyleCop, i campi, le proprietà, i metodi privati, i costruttori, ecc. Sono già raggruppati e facili da trovare. In caso contrario, è il momento di iniziare a pensare di applicare regole che garantiscano l'uniformità in tutta la base di codice.
Altre persone usano le regioni per nascondere molte entità simili . Ad esempio, quando hai una classe con centinaia di campi (che crea almeno 500 righe di codice se conti i commenti e lo spazio bianco), potresti essere tentato di mettere quei campi all'interno di una regione, comprimerlo e dimenticartene. Ancora una volta, stai sbagliando: con così tanti campi in una classe, dovresti pensare meglio all'uso dell'ereditarietà o suddividere l'oggetto in più oggetti.
Infine, alcune persone sono tentate di usare le regioni per raggruppare cose correlate : un evento con il suo delegato o un metodo correlato all'IO con altri metodi relativi all'IO, ecc. Nel primo caso, diventa un casino che è difficile da mantenere , leggi e capisci. Nel secondo caso, il miglior progetto sarebbe probabilmente quello di creare diverse classi.
C'è un buon uso per le regioni?
No. C'era un uso legacy: codice generato. Tuttavia, gli strumenti di generazione del codice devono solo utilizzare classi parziali. Se C # ha il supporto per le regioni, è principalmente dovuto a questo uso legacy e perché ora che troppe persone hanno usato le regioni nel loro codice, sarebbe impossibile rimuoverle senza rompere le basi di codice esistenti.
Pensa a questo proposito goto
. Il fatto che la lingua o l'IDE supporti una funzione non significa che debba essere utilizzata quotidianamente. La regola StyleCop SA1124 è chiara: non si devono usare le regioni. Mai.
Esempi
Attualmente sto facendo una revisione del codice del codice del mio collega. La base di codice contiene molte regioni ed è in realtà un perfetto esempio sia di come non utilizzare le regioni sia del perché le regioni portano a codici errati. Ecco alcuni esempi:
4000 mostro LOC:
Di recente ho letto da qualche parte su Programmers.SE che quando un file contiene troppi using
s (dopo aver eseguito il comando "Rimuovi Usi inutilizzati"), è un buon segno che la classe all'interno di questo file sta facendo troppo. Lo stesso vale per le dimensioni del file stesso.
Durante la revisione del codice, mi sono imbattuto in un file LOC da 4 000. Sembra che l'autore di questo codice abbia semplicemente copiato e incollato lo stesso metodo a 15 righe centinaia di volte, modificando leggermente i nomi delle variabili e il metodo chiamato. Un semplice regex ha permesso di tagliare il file da 4000 LOC a 500 LOC, semplicemente aggiungendo alcuni generici; Sono abbastanza sicuro che con un refactoring più intelligente, questa classe potrebbe essere ridotta a poche decine di righe.
Usando le regioni, l'autore si è incoraggiato a ignorare il fatto che il codice è impossibile da mantenere e scritto male, e a duplicare pesantemente il codice invece di riformattarlo.
Regione “Do A”, Regione “Do B”:
Un altro esempio eccellente è stato un metodo di inizializzazione dei mostri che ha semplicemente svolto l'attività 1, quindi l'attività 2, quindi l'attività 3, ecc. Vi erano cinque o sei attività totalmente indipendenti, ciascuna delle quali inizializzava qualcosa in una classe container. Tutte queste attività sono state raggruppate in un metodo e raggruppate in regioni.
Questo ha avuto un vantaggio:
- Il metodo era abbastanza chiaro da capire guardando i nomi delle regioni. Detto questo, lo stesso metodo, una volta refactored, sarebbe chiaro come l'originale.
I problemi, d'altra parte, erano molteplici:
Non era ovvio se esistessero dipendenze tra le regioni. Speriamo che non ci fosse riutilizzo delle variabili; altrimenti, la manutenzione potrebbe essere un incubo ancora di più.
Il metodo era quasi impossibile da testare. Come faresti facilmente a sapere se il metodo che fa venti cose alla volta le fa correttamente?
Regione dei campi, regione delle proprietà, regione del costruttore:
Il codice rivisto conteneva anche molte regioni che raggruppavano tutti i campi insieme, tutte le proprietà insieme, ecc. Questo aveva un ovvio problema: la crescita del codice sorgente.
Quando apri un file e vedi un enorme elenco di campi, sei più propenso a refactificare prima la classe, quindi lavorare con il codice. Con le regioni, prendi l'abitudine di far crollare cose e dimenticartene.
Un altro problema è che se lo fai ovunque, ti ritroverai a creare regioni a un blocco, il che non ha alcun senso. Questo è stato in realtà il caso del codice che ho recensito, in cui c'erano molti #region Constructor
contenuti contenenti un costruttore.
Infine, campi, proprietà, costruttori ecc. Dovrebbero già essere in ordine . Se lo sono e corrispondono alle convenzioni (costanti che iniziano con una lettera maiuscola, ecc.), È già chiaro dove il tipo di elementi si interrompe e altri iniziano, quindi non è necessario creare esplicitamente regioni per quello.