Ein Codegeruch ist ein Symptom, das darauf hinweist, dass das Design ein Problem aufweist, das möglicherweise die Anzahl der Fehler erhöht. Dies gilt nicht für Regionen, aber Regionen können wie lange Methoden zur Entstehung von Codegerüchen beitragen.
Schon seit:
Ein Antimuster (oder Antimuster) ist ein Muster, das in sozialen oder geschäftlichen Abläufen oder in der Softwareentwicklung verwendet wird und häufig verwendet wird, in der Praxis jedoch ineffektiv und / oder kontraproduktiv ist
Regionen sind Anti-Muster. Sie erfordern mehr Arbeit, was weder die Qualität noch die Lesbarkeit des Codes erhöht, die Anzahl der Fehler nicht verringert und die Umgestaltung des Codes möglicherweise komplizierter macht.
Verwenden Sie keine Regionen innerhalb von Methoden. Refactor statt
Methoden müssen kurz sein . Wenn eine Methode nur zehn Zeilen enthält, würden Sie wahrscheinlich keine Regionen verwenden, um fünf davon auszublenden, wenn Sie an den anderen fünf arbeiten.
Außerdem muss jede Methode eine und eine einzige Sache tun . Regionen hingegen sollen verschiedene Dinge trennen . Wenn Ihre Methode A und dann B ausführt, ist es logisch, zwei Regionen zu erstellen. Dies ist jedoch ein falscher Ansatz. Stattdessen sollten Sie die Methode in zwei separate Methoden umgestalten.
Die Verwendung von Regionen kann in diesem Fall auch das Refactoring erschweren. Stellen Sie sich vor, Sie haben:
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
}
Das Einklappen der ersten Region, um sich auf die zweite zu konzentrieren, ist nicht nur riskant: Wir können leicht die Ausnahme vergessen, die den Fluss stoppt (es könnte eine Schutzklausel mit einem return
stattdessen geben, die noch schwieriger zu erkennen ist), sondern es würde auch ein Problem geben Wenn der Code auf diese Weise überarbeitet werden soll:
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
}
Jetzt machen Regionen keinen Sinn, und Sie können den Code in der zweiten Region möglicherweise nicht lesen und verstehen, ohne den Code in der ersten zu betrachten.
Ein anderer Fall, den ich manchmal sehe, ist dieser:
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
}
Es ist verlockend, Regionen zu verwenden, wenn die Validierung von Argumenten sich über mehrere zehn LOC erstreckt. Es gibt jedoch einen besseren Weg, um dieses Problem zu lösen: den, der vom .NET Framework-Quellcode verwendet wird:
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)
{
...
}
Verwenden Sie zum Gruppieren keine Regionen außerhalb der Methoden
Einige Leute verwenden sie, um Felder, Eigenschaften usw. zu gruppieren. Dieser Ansatz ist falsch: Wenn Ihr Code StyleCop-kompatibel ist, sind Felder, Eigenschaften, private Methoden, Konstruktoren usw. bereits gruppiert und leicht zu finden. Wenn dies nicht der Fall ist, sollten Sie darüber nachdenken, Regeln anzuwenden, die die Einheitlichkeit Ihrer Codebasis gewährleisten.
Andere Leute benutzen Regionen, um viele ähnliche Wesen zu verbergen . Wenn Sie beispielsweise eine Klasse mit hundert Feldern haben (die mindestens 500 Codezeilen enthält, wenn Sie die Kommentare und das Leerzeichen zählen), könnten Sie versucht sein, diese Felder in eine Region einzufügen, sie zu reduzieren und sie zu vergessen. Wiederum machen Sie es falsch: Bei so vielen Feldern in einer Klasse sollten Sie besser über die Verwendung der Vererbung nachdenken oder das Objekt in mehrere Objekte aufteilen.
Schließlich sind einige Leute versucht, Regionen zu verwenden , um verwandte Dinge zu gruppieren : ein Ereignis mit seinem Delegierten oder eine Methode in Bezug auf E / A mit anderen Methoden in Bezug auf E / A usw. Im ersten Fall wird es zu einem Chaos, das schwer zu pflegen ist , lesen und verstehen. Im zweiten Fall wäre es wahrscheinlich besser, mehrere Klassen zu erstellen.
Gibt es eine gute Verwendung für Regionen?
Nein, es gab eine Legacy-Verwendung: generierten Code. Bei Tools zur Codegenerierung müssen jedoch nur Teilklassen verwendet werden. Wenn C # Regionen unterstützt, liegt dies hauptsächlich daran, dass diese Legacy-Verwendung verwendet wird, und dass es unmöglich ist, Regionen zu entfernen, ohne vorhandene Codebasen zu beschädigen, da zu viele Benutzer Regionen in ihrem Code verwendet haben.
Denken Sie darüber nach goto
. Die Tatsache, dass die Sprache oder die IDE eine Funktion unterstützt, bedeutet nicht, dass sie täglich verwendet werden sollte. Die StyleCop SA1124-Regel ist klar: Sie sollten keine Regionen verwenden. Noch nie.
Beispiele
Ich führe gerade eine Codeüberprüfung des Codes meines Kollegen durch. Die Codebasis enthält viele Regionen und ist ein perfektes Beispiel dafür, wie Regionen nicht verwendet werden und warum Regionen zu schlechtem Code führen. Hier sind einige Beispiele:
4 000 LOC Monster:
Ich habe kürzlich irgendwo auf Programmers.SE gelesen, dass, wenn eine Datei zu viele using
s enthält (nachdem der Befehl "Remove Unused Usings" ausgeführt wurde), dies ein gutes Zeichen dafür ist, dass die Klasse in dieser Datei zu viel tut. Gleiches gilt für die Größe der Datei.
Bei der Überprüfung des Codes bin ich auf eine 4 000 LOC-Datei gestoßen. Es stellte sich heraus, dass der Autor dieses Codes dieselbe 15-Zeilen-Methode einfach hunderte Male kopiert und dabei die Namen der Variablen und die aufgerufene Methode leicht geändert hat. Ein einfacher regulärer Ausdruck erlaubt das Trimmen der Datei von 4 000 LOC auf 500 LOC, indem nur ein paar Generika hinzugefügt werden. Ich bin mir ziemlich sicher, dass diese Klasse mit einem cleveren Refactoring auf ein paar Dutzend Zeilen reduziert werden kann.
Durch die Verwendung von Regionen ermutigte sich der Autor, die Tatsache zu ignorieren, dass der Code nicht gewartet und schlecht geschrieben werden kann, und den Code stark zu duplizieren, anstatt ihn umzugestalten.
Region "Do A", Region "Do B":
Ein weiteres hervorragendes Beispiel war eine Monster-Initialisierungsmethode, die einfach Aufgabe 1, dann Aufgabe 2, dann Aufgabe 3 usw. ausführte. Es gab fünf oder sechs Aufgaben, die völlig unabhängig voneinander waren und jeweils etwas in einer Containerklasse initialisierten. Alle diese Aufgaben wurden in einer Methode und in Regionen zusammengefasst.
Dies hatte einen Vorteil:
- Die Methode war anhand der Regionsnamen ziemlich klar zu verstehen. Abgesehen davon wäre dieselbe Methode, die einmal überarbeitet wurde, genauso klar wie die ursprüngliche.
Die Probleme waren andererseits vielfältig:
Es war nicht offensichtlich, ob es Abhängigkeiten zwischen den Regionen gab. Hoffentlich gab es keine Wiederverwendung von Variablen; Andernfalls könnte die Wartung noch mehr zum Albtraum werden.
Die Methode war kaum zu testen. Wie können Sie leicht feststellen, ob die Methode, die zwanzig Dinge gleichzeitig ausführt, diese korrekt ausführt?
Felder Region, Eigenschaften Region, Konstruktor Region:
Der überprüfte Code enthielt auch viele Regionen, in denen alle Felder, alle Eigenschaften usw. zusammengefasst waren. Dies hatte ein offensichtliches Problem: das Wachstum des Quellcodes.
Wenn Sie eine Datei öffnen und eine große Liste von Feldern sehen, neigen Sie eher dazu, die Klasse zuerst umzugestalten und dann mit Code zu arbeiten. Bei Regionen hat man die Angewohnheit, Dinge zusammenzubrechen und zu vergessen.
Ein weiteres Problem ist, dass Sie, wenn Sie es überall tun, Ein-Block-Regionen erstellen, was keinen Sinn ergibt. Dies war tatsächlich der Fall in dem Code, den ich überprüft habe, in dem viele #region Constructor
einen Konstruktor enthielten.
Schließlich sollten Felder, Eigenschaften, Konstruktoren usw. bereits in Ordnung sein . Wenn dies der Fall ist und sie mit den Konventionen übereinstimmen (Konstanten, die mit einem Großbuchstaben beginnen usw.), ist bereits klar, an welcher Stelle der Elementtyp anhält und an welcher Stelle der andere beginnt, sodass Sie hierfür keine expliziten Regionen erstellen müssen.