Die menschlichste Art, Definitionen von Klassenmethoden zu bestellen?

38

In jeder Klassendefinition habe ich die Methodendefinitionen auf verschiedene Arten sortiert gesehen: alphabetisch, chronologisch basierend auf der häufigsten Verwendung, alphabetisch sortiert nach Sichtbarkeit, alphabetisch sortiert nach Gettern und Setzern usw. Wenn ich anfange, eine neue Klasse zu schreiben, Ich neige dazu, einfach alles einzutippen und dann neu zu ordnen, wenn ich mit dem Schreiben der gesamten Klasse fertig bin. In diesem Sinne habe ich drei Fragen:

  1. Kommt es auf die Bestellung an?
  2. Gibt es eine "beste" Bestellung?
  3. Ich vermute, es gibt keine, also was sind die Vor- und Nachteile der verschiedenen Bestellstrategien?
Johntron
quelle
1
Sie erwarten nicht wirklich, dass Leute Code ausschneiden / einfügen, um Methoden neu anzuordnen. Wenn die IDE das automatisch macht, ist das in Ordnung. Ansonsten ist es schwer durchzusetzen.
Reactgular
Zur Verdeutlichung geht es bei meiner Frage um Lesbarkeit / Benutzerfreundlichkeit, nicht um Syntax.
Johntron

Antworten:

52

In einigen Programmiersprachen spielt die Reihenfolge eine Rolle, da Sie die Dinge erst verwenden können, nachdem sie deklariert wurden. Aber abgesehen davon ist es für die meisten Sprachen für den Compiler unerheblich. Dann bleibt es für die Menschen von Bedeutung.

Mein Lieblingszitat von Martin Fowler lautet: Any fool can write code that a computer can understand. Good programmers write code that humans can understand.Also würde ich sagen, dass die Reihenfolge Ihrer Klasse davon abhängen sollte, was es den Menschen leicht macht, sie zu verstehen.

Ich persönlich bevorzuge die Absenkungsbehandlung, die Bob Martin in seinem Clean CodeBuch gibt. Mitgliedsvariablen am oberen Rand der Klasse, dann Konstruktoren, dann alle anderen Methoden. Und Sie ordnen die Methoden so an, dass sie eng zusammenpassen mit der Art und Weise, wie sie in der Klasse verwendet werden (anstatt willkürlich alle öffentlichen als auch privaten als geschützten Methoden zu setzen). Er nennt es den "vertikalen Abstand" minimieren oder so ähnlich (habe das Buch im Moment nicht bei mir).

Bearbeiten:

Die Grundidee von "vertical distance" ist, dass Sie vermeiden möchten, dass Leute um Ihren Quellcode herum springen, nur um ihn zu verstehen. Wenn die Dinge zusammenhängen, sollten sie näher beieinander liegen. Dinge, die nichts miteinander zu tun haben, können weiter voneinander entfernt sein.

Kapitel 5 von Clean Code (großartiges Buch, nebenbei) befasst sich ausführlich mit den Vorschlägen von Mr. Martin für den Bestellcode. Er schlägt vor, dass das Lesen von Code wie das Lesen eines Zeitungsartikels funktionieren sollte: Die Details auf hoher Ebene stehen an erster Stelle (oben) und Sie erhalten mehr Details, wenn Sie nachlesen. Er sagt: "Wenn eine Funktion eine andere aufruft, sollten sie sich vertikal in der Nähe befinden, und der Anrufer sollte sich, wenn überhaupt möglich, über dem Angerufenen befinden." Darüber hinaus sollten verwandte Konzepte eng beieinander liegen.

Hier ist also ein ausgedachtes Beispiel, das in vielerlei Hinsicht schlecht ist (schlechtes OO-Design; niemals doublefür Geld verwenden), aber die Idee veranschaulicht:

public class Employee {
  ...
  public String getEmployeeId() { return employeeId; }
  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }

  public double calculatePaycheck() {
    double pay = getSalary() / PAY_PERIODS_PER_YEAR;
    if (isEligibleForBonus()) {
      pay += calculateBonus();
    }
    return pay;
  }

  private double getSalary() { ... }

  private boolean isEligibleForBonus() {
    return (isFullTimeEmployee() && didCompleteBonusObjectives());
  }

  public boolean isFullTimeEmployee() { ... }
  private boolean didCompleteBonusObjectives() { ... }
  private double calculateBonus() { ... }
}

Die Methoden sind so angeordnet, dass sie denen, die sie aufrufen, sehr nahe kommen und sich von oben nach unten bewegen. Wenn wir alle privateMethoden unterhalb publicderjenigen platziert hätten, müssten Sie mehr herumspringen, um dem Programmfluss zu folgen.

getFirstNameund getLastNamesind konzeptionell verwandt (und getEmployeeIdwahrscheinlich auch), so dass sie nahe beieinander liegen. Wir könnten sie alle nach unten verschieben, aber wir möchten nicht getFirstNameoben und getLastNameunten sehen.

Hoffentlich gibt Ihnen dies die Grundidee. Wenn Sie an solchen Dingen interessiert sind, empfehle ich dringend, sie zu lesen Clean Code.

Allan
quelle
Ich muss wissen, wie Setter und Getter von Instanzvariablen platziert werden müssen. Sollte es direkt nach dem Klassenkonstruktor oder am Ende der Klasse stehen?
Srinivas
Persönlich mag ich sie ganz oben nach dem Konstrukteur. Aber es ist nicht wirklich wichtig; Ich würde Konsistenz in Ihrem Projekt und mit Ihrem Team empfehlen, um eine gute Entscheidung zu treffen.
Allan
Sollte calculateBonus()vorher nicht kommen isFullTimeEmployee()und didCompleteBonusObjectives()?
Winklerrr
@winklerrr Ich kann ein Argument dafür sehen. Ich habe sie dort platziert, wo ich sie gemacht habe, isFullTimeEmployeeund sie didCompleteBonusObjectiveswerden von dort verwendet, isEligibleForBonusso dass sie vertikal in der Nähe davon sein sollten. Aber Sie könnten möglicherweise nach calculateBonusoben gehen, um es näher an den Ort zu bringen, an dem es heißt. Leider kommt es bei Funktionen, die Funktionen aufrufen, irgendwann zu Problemen (wie bei einer gemeinsamen Funktion, die von mehreren anderen aufgerufen wird), bei denen die Reihenfolge nicht perfekt ist. Dann ist es Ihrem besten Urteil überlassen. Ich empfehle, Klassen und Funktionen klein zu halten, um diese Probleme zu mindern.
Allan
2

Ich ordne meine Methoden in der Regel nach Beziehung und Verwendungsreihenfolge.

Nehmen Sie eine Netzwerkklasse, ich werde alle TCP-Methoden und dann alle UDP-Methoden zusammen gruppieren. Beim internen TCP hätte ich die Setup-Methode als erste, vielleicht sende ich eine gegebene Nachricht als zweite und schließe den TCP-Socket als dritte.

Natürlich passen nicht alle Klassen in dieses Muster, aber das ist mein allgemeiner Arbeitsablauf.

Ich mache das so, um mehr als alles andere zu debuggen. Wenn ich ein Problem habe und zur Methode springen möchte, denke ich nicht, wie es geschrieben wird, ich denke, wofür es verantwortlich ist, und gehe zu diesem Abschnitt.

Insbesondere auf diese Weise ist es für Dritte sinnvoll, Ihren Code als Gruppierung anzuzeigen / zu verwenden und die Reihenfolge zu befolgen, in der er verwendet wird. Daher folgt der Code, den sie mit Ihrer Klasse schreiben, der gleichen Struktur wie die Klasse.

In Bezug darauf spielt es eine Rolle?

auf jeden Fall für die Lesbarkeit.

ansonsten nicht wirklich, nur in Fällen, in denen bestimmte Sprachen die Methode nicht aufrufen können, es sei denn, sie ist oben definiert, wo sie aufgerufen wird usw.

Simon McLoughlin
quelle
0

Idealerweise sind Ihre Klassen so klein, dass es keine Rolle spielt. Wenn Sie nur ein Dutzend Methoden haben und Ihr Editor oder Ihre IDE das Falten unterstützt, haben Sie kein Problem, da die gesamte Liste der Methoden in 12 Zeilen passt.

Geschieht dies nicht, sollte die Unterscheidung auf oberster Ebene öffentlich oder privat sein. Führen Sie zuerst die öffentlichen Methoden auf: Diese werden am häufigsten gesucht, da sie die Art und Weise definieren, wie Ihre Klasse mit dem Rest der Codebasis kommuniziert.

Innerhalb jeder dieser Funktionen ist es am sinnvollsten, Methoden nach Funktionalität zu gruppieren: Konstruktoren und Destruktoren in einem Block, Getter / Setter in einem anderen, Überladungen von Operatoren, statische Methoden und den Rest der Gruppe. In C ++ halte ich mich jedoch gerne an operator=die Konstruktoren, da diese eng mit dem Kopierkonstruktor verwandt sind und ich schnell erkennen möchte, ob alle (oder keine) Standard-ctor, copy ctor, operator = und dtor vorhanden sind existieren.

tdammers
quelle
-1

tl; dr

Nur wenn die Sprache, in der Sie arbeiten, eine bestimmte Bestellung erfordert. Ansonsten liegt die Bestellung bei Ihnen. Wählen Sie ein System, das konsistent und sinnvoll ist, und versuchen Sie, so weit wie möglich daran festzuhalten.


1. Kommt es auf die Bestellung an?

Nur wenn für die Sprache, in der Sie arbeiten, eine Funktion definiert sein muss, die früher in der Datei definiert ist als dort, wo sie aufgerufen wird, wie in diesem Beispiel:

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

Sie erhalten eine Fehlermeldung, weil Sie anrufen, funcB()bevor Sie es verwenden. Ich denke, dies ist ein Problem in PL / SQL und möglicherweise auch in C, aber Sie können Vorwärtserklärungen haben wie:

void funcB();

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

Dies ist die einzige Situation, die mir einfällt, wenn die Bestellung "falsch" ist und Sie nicht einmal kompilieren können.

Ansonsten können Sie sie jederzeit nachbestellen. Sie könnten wahrscheinlich sogar ein Tool schreiben, das dies für Sie erledigt (wenn Sie dort kein Tool finden können).

2. Gibt es eine "beste" Bestellung?

Wenn die Sprache / Umgebung keine Bestellanforderungen hat, funktioniert die "beste" Reihenfolge am besten für Sie . Für mich mag ich es, alle Getter / Setter zusammen zu haben, normalerweise zu Beginn der Klasse (aber nach Konstruktoren / statischen Initialisierern), und dann die privaten Methoden, dann protected, dann public. In jeder bereichsbezogenen Gruppe gibt es normalerweise keine Reihenfolge, obwohl überladene Methoden, die ich zusammenzuhalten versuche, in der Reihenfolge der Anzahl der Parameter. Ich versuche auch, Methoden mit verwandten Funktionen zusammenzuhalten, obwohl ich manchmal meine bereichsbezogene Reihenfolge brechen muss, um dies zu tun. und manchmal wird versucht, bereichsbezogene Sortierpausen nach Funktionen zu verwalten. Und meine IDE kann mir eine alphabetische Gliederungsansicht geben, das ist auch gut so. ;)

Einige Sprachen, wie C #, haben die Möglichkeit, Code in "Regionen" zu gruppieren , die keine Auswirkungen auf die Kompilierung haben, aber es möglicherweise einfacher machen, verwandte Funktionen zusammenzuhalten und sie dann mit der IDE auszublenden / anzuzeigen. Wie MainMa betonte , gibt es einige, die dies für eine schlechte Praxis halten. Ich habe gute und schlechte Beispiele dafür gesehen, wie Regionen auf diese Weise verwendet werden. Wenn Sie also auf diese Weise vorgehen, seien Sie vorsichtig.

FrustratedWithFormsDesigner
quelle