it-swarm-eu.dev

Warum sind Menschen in Methoden so stark gegen # region-Tags?

Ich höre viel darüber, Methoden kurz zu halten, und ich habe viele Programmierer sagen hören, dass die Verwendung von # region-Tags innerhalb einer Methode ein sicheres Zeichen dafür ist, dass sie zu lang ist und in mehrere Methoden umgestaltet werden sollte. Es scheint mir jedoch, dass es viele Fälle gibt, in denen das Trennen von Code mit # region-Tags innerhalb einer Methode die überlegene Lösung für das Refactoring in mehrere Methoden ist.

Angenommen, wir haben eine Methode, deren Berechnung in drei ziemlich unterschiedliche Phasen unterteilt werden kann. Darüber hinaus ist jede dieser Stufen nur für die Berechnung der Methode this relevant. Wenn Sie sie also in neue Methoden extrahieren, erhalten Sie keine Code-Wiederverwendung. Was sind dann die Vorteile, wenn jede Phase in eine eigene Methode extrahiert wird? Soweit ich das beurteilen kann, erhalten wir nur eine gewisse Lesbarkeit und einen separaten variablen Bereich für jede Phase (wodurch verhindert wird, dass Änderungen einer bestimmten Phase versehentlich eine andere Phase unterbrechen).

Beides kann jedoch erreicht werden, ohne jede Phase in ein eigenes Verfahren zu extrahieren. Mit Region-Tags können wir den Code in ein ebenso lesbares Formular reduzieren (mit dem zusätzlichen Vorteil, dass wir unseren Platz in dieser Datei nicht mehr verlassen müssen, wenn wir uns entscheiden, den Code zu erweitern und zu untersuchen) und einfach jede Phase einschließen {} erstellt einen eigenen Arbeitsbereich.

Der Vorteil dieser Vorgehensweise besteht darin, dass wir den Bereich auf Klassenebene nicht mit drei Methoden verschmutzen, die eigentlich nur für das Innenleben einer vierten Methode relevant sind. Die sofortige Umgestaltung einer langen Methode in eine Reihe von kurzen Methoden scheint mir die Wiederverwendung von Code zu sein, die einer vorzeitigen Optimierung entspricht. Sie führen zusätzliche Komplexität ein, um ein Problem anzugehen, das in vielen Fällen nie auftritt. Sie können später jederzeit eine der Phasen in eine eigene Methode extrahieren, falls sich die Möglichkeit zur Wiederverwendung von Code ergibt.

Gedanken?

27
jjoelson

Alles, was Sie jemals interessieren sollten, ist, dass Ihr Code verwendbar und nicht wiederverwendbar ist. Ein Affe kann verwendbaren Code in wiederverwendbaren Code umwandeln, wenn überhaupt Transformationen durchgeführt werden müssen.

Das Argument "Ich brauche das nur hier" ist schlecht, um es höflich auszudrücken. Die Technik, die Sie beschreiben, wird oft als Überschriftentechnik bezeichnet und ist im Allgemeinen verpönt.

  1. Sie können Regionen nicht testen, aber Sie können echte Methoden isoliert testen.
  2. Regionen sind Kommentare, keine syntaktischen Elemente. Im schlimmsten Fall widersprechen sich die Verschachtelung Ihrer Regionen und Ihrer Blöcke. Sie sollten sich immer bemühen, die Semantik Ihrer Struktur mit den syntaktischen Elementen der von Ihnen verwendeten Sprache darzustellen.
  3. Nach dem Refactoring zu Methoden muss nicht mehr gefaltet werden, um den Text zu lesen. Möglicherweise wird der Quellcode in einem Tool ohne Faltung betrachtet, z. B. in einem Terminal, einem E-Mail-Client, einer Webansicht für Ihr VCS oder einem Diff-Viewer.
  4. Nach dem Refactoring auf Methoden ist die resultierende Methode mindestens so gut wie bei Regionen. Außerdem ist es einfacher, die einzelnen Teile zu verschieben.
  5. Das Prinzip der Einzelverantwortung schlägt vor, dass jede Einheit nur eine Aufgabe und eine Aufgabe haben sollte. Die Aufgabe der "Haupt" -Methode besteht darin, die "Hilfs" -Methoden zusammenzustellen, um das gewünschte Ergebnis zu erzielen. Die "Hilfs" -Methoden lösen ein diskretes, einfaches Problem isoliert. Die Klasse, die alle diese Methoden enthält, sollte auch nur eine Aufgabe erfüllen und nur die Methoden enthalten, die sich auf diese Aufgabe beziehen. Die Hilfsmethoden gehören also entweder zum Klassenbereich, oder der Code sollte nicht in erster Linie in der Klasse enthalten sein, sondern zumindest in einer globalen Methode oder, noch besser, injiziert .

Ebenfalls relevant: Jeff Atwoods 'Gedanken zur Code-Faltung

66
back2dos

Es sind nicht die Regionen in den Methoden, die das Problem darstellen, sondern der Fall, wenn es notwendig (oder sogar sinnvoll) ist, Regionen in Methoden einzufügen.

Nachdem ich viele 200-300 Zeilenmethoden debuggen musste, kann ich Ihnen sagen, dass ich es niemandem wünschen würde. Wenn Sie Ihren eigenen Code schreiben und pflegen, ist das in Ordnung - machen Sie, was Sie wollen. Aber wenn jemand anderes es sich ansieht, sollte sofort klar sein, was eine Methode tut.

"Zuerst bekommt es die Dinge, und dann bewegt es sie herum, dann, wenn sie summen müssen, macht es das, sonst prüft es, ob sie blau sind und kehrt ihren Copernicus-Wert um. Wenn das zweite Ding größer ist als das erste, wir Ich muss die Saftbox holen und sie einsetzen ... "

Nein, das ist nicht gut genug. Teilen Sie es in Regionen auf, die Sie wollen, aber ich schüttle immer noch den Kopf, wenn ich nur daran denke.

Wenn Sie in Methoden einbrechen, erhalten Sie viele Vorteile:

  • Klareres Verständnis dessen, was wo passiert, auch wenn nur durch den Namen der Methode. Und Sie sind falsch, dass eine Methode nicht vollständig dokumentiert werden kann - fügen Sie den Dokumentationsblock hinzu (drücken Sie ///) Und geben Sie die Beschreibung, die Parameter, den Rückgabetyp und auch exception, example, remarks Knoten, um diese Szenarien detailliert darzustellen. Da geht es hin, dafür ist es da!
  • Es gibt keine Nebenwirkungen oder Scoping-Probleme. Sie können sagen, dass Sie alle Ihre Variablen in einem Unterbereich mit {} Deklarieren, aber muss ich jede Methode überprüfen, um sicherzustellen, dass Sie nicht "betrogen" haben? Wird dieses dodgyObject hier nur verwendet, weil es in dieser Region verwendet wird, oder stammt es von einem anderen Ort? Wenn ich in meiner eigenen Methode wäre, könnte ich leicht erkennen, ob es an mich weitergegeben wurde oder ob ich es selbst erstellt habe (oder ob Sie es erstellt haben).
  • In meinem Ereignisprotokoll wird angegeben, bei welcher Methode eine Ausnahme aufgetreten ist. Ja, ich kann möglicherweise den fehlerhaften Code mit einer Zeilennummer finden, aber wenn ich den Methodennamen kenne (insbesondere, wenn ich sie richtig benannt habe), kann ich sehr gut darauf hinweisen Was ist passiert und was ist schief gelaufen? "Oh, die TurnThingsUpsideDown -Methode ist fehlgeschlagen - sie schlägt wahrscheinlich fehl, während eine schlechte Sache gedreht wird" ist viel besser als "Oh, die DoEverything -Methode ist fehlgeschlagen - es könnten 50 verschiedene Dinge gewesen sein und ich ' Ich muss eine Stunde lang herumgraben, um es zu finden ".

Dies alles vor Bedenken hinsichtlich Wiederverwendbarkeit oder Verbesserbarkeit. Richtig getrennte Methoden erleichtern offensichtlich die Wiederverwendung und ermöglichen es Ihnen, eine nicht leistungsfähige Methode (zu langsam, zu fehlerhaft, geänderte Abhängigkeit usw.) einfach zu ersetzen. Haben Sie jemals versucht, eine dieser riesigen Methoden umzugestalten? Es gibt keine Möglichkeit zu wissen, dass das, was Sie ändern, nichts anderes bewirkt.

Bitte kapseln Sie Ihre Logik ordnungsgemäß in Methoden mit angemessener Größe. Ich weiß, dass es für sie leicht ist, außer Kontrolle zu geraten, und zu argumentieren, dass es sich zu diesem Zeitpunkt nicht lohnt, sie einmal neu zu gestalten, ist ein weiteres Problem. Sie müssen jedoch die Tatsache akzeptieren, dass es keinen Schaden und zumindest potenziellen Nutzen bringt, wenn Sie richtig gekapselte, sauber geschriebene und einfach gestaltete Methoden haben.

15
Kirk Broadhurst

Wenn Ihre Methode so komplex ist, dass sie in drei verschiedene Phasen unterteilt werden kann, sollten dies nicht nur drei separate Methoden sein, sondern Ihre Methode sollte tatsächlich eine separate Klasse sein, die dieser Berechnung gewidmet ist.

"Aber dann wird meine Klasse nur eine öffentliche Methode haben!"

Du hast recht und daran ist nichts auszusetzen. Die Idee des Prinzips der Einzelverantwortung ist, dass Ihre Klasse eine Sache tut .

Ihre Klasse kann nacheinander jede der drei Methoden aufrufen und das Ergebnis zurückgeben. Eine Sache.

Als Bonus ist Ihre Methode jetzt testbar, da sie keine private Methode mehr ist.

Aber egal eine Klasse; In Wirklichkeit sollten Sie vier haben: Eine für jedes Teil Ihrer Methode plus eine, die jede der drei als Abhängigkeit nimmt und sie aufruft die richtige Reihenfolge, das Ergebnis zurückgeben.

Dies macht Ihre Klassen sogar mehr Testbar. Es macht es Ihnen auch leicht, eines dieser drei Teile zu wechseln. Schreiben Sie einfach eine neue Klasse, die von der alten erbt, und überschreiben Sie das geänderte Verhalten. Oder erstellen Sie eine Schnittstelle für das Verhalten jedes Ihrer drei Teile. Um eine zu ersetzen, schreiben Sie einfach eine neue Klasse, die diese Schnittstelle implementiert, und ersetzen Sie die alte Klasse in Ihrer Konfiguration des Abhängigkeitsinjektionscontainers.

Was? Sie verwenden keine Abhängigkeitsinjektion? Nun, Sie haben die Notwendigkeit wahrscheinlich noch nicht gesehen, weil Sie nicht dem Prinzip der Einzelverantwortung folgen. Sobald Sie beginnen, werden Sie feststellen, dass der einfachste Weg, jeder Klasse ihre Abhängigkeiten zuzuweisen, die Verwendung eines DI-Containers ist.

[~ # ~] edit [~ # ~] :

OK, lassen Sie uns die Refactoring-Frage beiseite legen. Der Zweck von #region Ist das Ausblenden von Code , was hauptsächlich Code bedeutet, der unter normalen Umständen nicht bearbeitet werden muss. Generierter Code ist das beste Beispiel. Es sollte in Regionen ausgeblendet sein, da Sie es normalerweise nicht bearbeiten müssen. Wenn Sie dies tun müssen, erhalten Sie durch das Erweitern einer Region eine Warnung im Stil "Hier sind Drachen", um sicherzustellen, dass Sie wissen, was Sie tun.

Daher würde ich sagen, dass #region nicht mitten in einer Methode verwendet werden sollte. Wenn ich eine Methode öffne, möchte ich den Code sehen. Die Verwendung von #region gibt mir eine weitere Ebene des Versteckens, die ich nicht benötige, und das wird ärgerlich: Ich entfalte die Methode ... und kann immer noch keinen Code sehen.

Wenn Sie sich Sorgen machen, dass die Struktur der Methode angezeigt wird (und Sie sich weigern, sie umzugestalten), fügen Sie folgende Bannerkommentare hinzu:

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

Diese helfen jemandem, der den Code durchsucht, die einzelnen Teile Ihrer Methode zu sehen, ohne sich mit dem Erweitern und Reduzieren von #region - Tags befassen zu müssen.

7
Kyralessa

Ich denke, das Beispiel, das Sie in Ihrer Argumentation geben, handelt weniger von YAGNI (Sie werden es nicht brauchen) als vielmehr vom Schreiben eines richtigen Qualitätscodes, der leicht zu warten ist.

In den frühesten Programmierkursen erfahren wir, dass eine Methode mit einer Länge von 700 LOC wahrscheinlich zu groß ist und sicherlich ein großer Aufwand wäre, um sie zu debuggen.

Zweitens, wenn ich Methoden A, B und C schreibe, die nur in Methode D verwendet werden, kann ich A B und C unabhängig von D einfacher einem Unit-Test unterziehen, was robustere Unit-Tests in allen 4 Methoden ermöglicht.

4
maple_shaft

Angenommen, wir haben eine Methode, deren Berechnung in drei ziemlich unterschiedliche Phasen unterteilt werden kann. Darüber hinaus ist jede dieser Stufen nur für die Berechnung dieser Methode relevant. Wenn Sie sie also in neue Methoden extrahieren, erhalten Sie keine Code-Wiederverwendung. Was sind dann die Vorteile, wenn jede Phase in eine eigene Methode extrahiert wird? Soweit ich das beurteilen kann, erhalten wir nur eine gewisse Lesbarkeit und einen separaten variablen Bereich für jede Phase (wodurch verhindert wird, dass Änderungen einer bestimmten Phase versehentlich eine andere Phase unterbrechen).

Das sind zwei Vorteile. Aber das Wichtigste ist für mich sowieso Debuggability. Wenn Sie diesen Code nachverfolgen müssen, ist es viel einfacher, zwei der drei Abschnitte zu durchlaufen und nur den zu verfolgen, der Ihnen am Herzen liegt. Das Refactoring in kleinere Methoden ermöglicht dies; Regions-Tags nicht.

3
Mason Wheeler

Meine persönliche Regel lautet: Wenn eine Methode länger als ein Bildschirm ist, beispielsweise 40 Zeilen, ist sie zu lang. Wenn eine Klasse länger als 500 Zeilen ist, ist sie zu groß. Ich verstoße manchmal gegen diese Regeln, gelegentlich sogar um ein großes Vielfaches, aber ich bereue es im Allgemeinen innerhalb eines Jahres.

Sogar eine Methode mit 20 bis 30 Zeilen wird in den meisten Fällen etwas lang. Daher würde ich sagen, dass die Verwendung von Regionen in einer Methode normalerweise eine schlechte Idee ist, einfach weil es fast nie sinnvoll wäre, eine Region mit 20 bis 30 Zeilen in mehrere Unterregionen zu reduzieren.

Das Aufteilen von Funktionen, die nach dieser Definition lang sind, hat mindestens zwei Vorteile:

  1. Sie können benennen, was diese Unterfunktionen tun, was Ihr derzeitiges Denken verdeutlicht und die Aufgabe späterer Betreuer vereinfacht.

  2. Wenn Sie in kleinere Funktionen zerlegen, müssen Sie nur die Parameter übergeben, die diese kleineren Funktionen benötigen. Daher ist der Umfang der Daten, die sie benötigen und ändern, sehr klar. Dies setzt voraus, dass Sie nicht in hundert verschiedenen Blattfunktionen auf den Objektstatus zugreifen und diesen ändern, was ich ebenfalls entmutigen würde.

Nach meiner Erfahrung erzeugen diese Regeln Code, der einfacher zu schreiben ist, ohne häufige Fehler zu machen, und der später verstanden und geändert werden kann, ohne subtile Verhaltensweisen zu brechen. Es spielt keine Rolle, ob die Person, die den Code verstehen/ändern muss, eine andere Person ist oder ich selbst, nachdem ich geschlafen und alles vergessen habe.

Daher würde ich Regionen in Methoden als "Code-Geruch" betrachten, der darauf hinweist, dass nicht nachhaltige Praktiken angewendet werden. Wie immer gibt es könnte Ausnahmen, aber sie kommen so selten vor, dass sie fast keine Diskussion wert sind.

2
PeterAllenWebb

Los geht's wieder ... Es gibt viele andere Themen hier über Programmierer, die in derselben Diskussion gelandet sind.

Sie weisen auf einige sehr interessante Argumente in Bezug auf kurze Funktionen hin. Es ist keine populäre Aussage, aber ich bin mit dir in dieser Hinsicht . Nachdem ich es schon oft besprochen habe, habe ich den Eindruck, dass die Diskussion in der Regel darauf hinausläuft, ob Sie sich hauptsächlich auf die richtige Kapselung konzentrieren oder die testgetriebene Entwicklung maximal nutzen. Dies sind die beiden Hauptkonkurrenten in einem weiteren heiligen Krieg, und ich fürchte, wir sind auf der untermächtigen Seite.

Jedoch ...

Die Lösung ist meiner Meinung nach definitiv nicht, die "Phasen" in Regionen zu umhüllen. Code-Faltung wird verwendet, um Code unter den Teppich zu kehren. Lassen Sie den Code so, wie er ist. Dies könnte Sie daran erinnern, dass Sie ihn möglicherweise zu einem späteren Zeitpunkt umgestalten möchten! Um es auf ein Argument in Ihrer Frage zu beziehen: Wie können Sie eine Möglichkeit zur Wiederverwendung von Code erkennen, wenn Sie keinen Code sehen? Lange Codesegmente sollten genau das sein, ein Dorn im Auge, der Sie dazu bringt, es umzugestalten.

Als geeigneten Ersatz für Regionen schlage ich vor, Code-Absätze zu verwenden, wie Alex Papadimoulis sie benennt in einem Kommentar . Möglicherweise verwenden Sie bereits einen ähnlichen Ansatz. Es sind einfach Codeblöcke mit einem Kommentar-Header, der erklärt, was der gesamte Block tut. Sie haben die Lesbarkeit von Funktionen, können jedoch normal verteilte englische Sätze verwenden und verlieren keine Kapselung.

2
Steven Jeuris

Obwohl ich zustimme, dass es normalerweise besser ist, Code umzugestalten, als #region, das ist nicht immer möglich.

Um diese Aussage zu unterstützen, möchte ich ein Beispiel geben.

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
  }

Es ist einfach, die Regionen neu anzupassen, um die Reihenfolge zu ändern oder zu erweitern, wenn der Klasse zusätzliche Felder hinzugefügt werden. Kommentare sind ohnehin erforderlich, um schnell zu sehen, wie die Bestellung funktionieren soll. Und vor allem: Ich sehe nicht, wie Refactoring in diesem Fall die Lesbarkeit verbessert.

Diese Ausnahmen sind jedoch selten. Normalerweise ist es möglich, eine Umgestaltung vorzunehmen, wie viele der anderen Antworten sagen.

1
Sjoerd

Ich glaube nicht, dass es eine einzige Antwort dafür gibt, warum bestimmte Personen oder Teams sich dafür entscheiden, #region Nicht zu verwenden, aber wenn ich einige auflisten müsste, wären dies die Hauptgründe, die ich gesehen habe.

  • Die Namen und die Reihenfolge der Regionen machen die Reihenfolge und Organisation des Codes deutlicher, wodurch ein bestimmter Stil erzwungen wird, der zu einer Quelle von Streit und Bikeshedding werden kann.
  • Die meisten Konzepte passen nicht sauber in eine kleine Anzahl von Regionen. Normalerweise beginnen Sie mit Regionen nach Zugriffsmodifikatoren public, privat, dann möglicherweise nach Elementtyp (z. B. Methode, Eigenschaft, Feld), aber was ist dann mit Konstruktoren, die diese Modifikatoren überspannen, statisch Sorten von all diesen, geschützten Mitgliedern, internen Mitgliedern, geschützten internen Mitgliedern, impliziten Schnittstellenmitgliedern, Ereignissen usw. Am Ende hätten Sie am besten gestaltete Klassen, in denen Sie aufgrund der detaillierten Kategorisierung in jeder Region eine einzelne oder eine Methode haben.
  • Wenn Sie in der Lage sind, pragmatisch zu bleiben und Dinge nur in drei oder vier konsistente Arten von Regionen zu gruppieren, kann das funktionieren, aber welchen Wert bringt es wirklich? Wenn Sie Klassen gut entwerfen, sollten Sie wirklich keine Codeseiten durchsuchen müssen.
  • Wie oben angedeutet, können Regionen hässlichen Code verbergen, was ihn weniger problematisch erscheinen lässt als er ist. Wenn Sie es als Augenschmerzen halten, kann dies dazu beitragen, dass es behoben wird.
0
jpierson