Verwendung der Vergangenheitsform (Passiv), um Unveränderlichkeit zu kennzeichnen? (ZB Array.Transposed vs. Array.Transpose)

8

[Hinweis: Das Folgende ist in seiner Syntax und Konventionen etwas C # -lastig, z. B. das Zitieren von FxCop-Regeln, da dies mein Hintergrund ist. Die Absicht dieser Diskussion ist jedoch, sprachunabhängig zu sein, und die verwendeten Codebeispiele sollten allgemein als solche angesehen werden Pseudocode. - Mike]

Ich denke, wir sind uns alle einig, dass Klassen im Allgemeinen so gestaltet werden sollten, dass ihre Objektinstanzen unveränderlich sind, wo immer dies praktikabel ist, und dass die Ergebnisse des Aufrufs von Klassenmitgliedern so weit wie möglich frei von Nebenwirkungen sein sollten.

Historische Namenskonventionen verwenden jedoch in der Regel die aktive Stimme: "GetSomething", "DoSomehing" usw. Dies ist gut, da darin klar angegeben ist, welche Maßnahmen das Klassenmitglied ergreifen wird.

Ich fange jetzt jedoch an zu denken, dass Mitglieder manchmal (oder möglicherweise oft) davon profitieren könnten, wenn sie in der Vergangenheitsform benannt werden, um zu kennzeichnen, dass ein Wert zurückgegeben wird, ohne das referenzierte Objekt zu beeinflussen.

Für eine Methode, die ein Array transponiert, wird es in aktuellen Namensstilen wahrscheinlich als "Array.Transpose" bezeichnet. Wenn dieses Mitglied jedoch ein neues Array zurückgibt , ohne das referenzierte Objekt zu transponieren, wird es meiner Meinung nach als "Array.Transposed" bezeichnet 'wäre genauer und macht es dem Programmierer klarer.

Wenn Sie mit einer 'Array.Transpose ()' - Methode arbeiten, wird möglicherweise versehentlich versucht, Code wie den folgenden zu verwenden:

Array myArray = new Array(); 
myArray.Transpose();

Wenn die 'Transpose'-Methode jedoch ein neues Array zurückgibt, ohne das referenzierte Objekt zu beeinflussen, führt dieser Code nichts aus - das heißt,' myArray 'wird stillschweigend nicht transponiert. (Ich nehme an, wenn 'Transponieren' eine Eigenschaft wäre, würde der Compiler feststellen, dass das oben Genannte nicht legal ist, aber die FxCop-Richtlinien besagen, dass ein Mitglied, das eine Konvertierung durchführt, eine Methode sein sollte.)

Wenn die Methode jedoch in der Vergangenheitsform als "Array.Transposed" bezeichnet würde, wäre für den Benutzer klarer, dass die folgende Syntax erforderlich ist:

Array myArray = new Array(); 
Array transposedArray = myArray.Transposed();

Andere Namen, von denen ich denke, dass sie in diese Kategorie passen, sind Mitglieder mit den Namen "Größe geändert" vs. "Größe ändern", "Verschoben" vs. "Verschiebung" usw. Die Gegenwart wird für Mitglieder verwendet, die das referenzierte Objekt betreffen, während die Vergangenheitsform wird für Mitglieder verwendet, die einen neuen Wert zurückgegeben haben, ohne das referenzierte Objekt zu beeinflussen.

Ein anderer Ansatz, der derzeit üblicher ist, besteht darin, jedem Namen "Get" oder "To" voranzustellen, aber ich denke, dass dies unnötiges Gewicht hinzufügt, während der Wechsel in die Vergangenheitsform die Bedeutung effektiv kommuniziert.

Ist dies für andere Programmierer hier sinnvoll, oder klingt diese Vergangenheitsform beim Lesen zu umständlich?

Bearbeiten:

Tolle Diskussionen unten, ich danke allen vielmals. Einige Punkte, die ich basierend auf Ihren Kommentaren und meinen eigenen überarbeiteten Überlegungen dazu zusammenfassen möchte, folgen unten.

Methode zur Verkettung "fließender" Syntaxen

Basierend auf den folgenden Diskussionen ist die Verwendung der Vergangenheitsform wahrscheinlich von Wert, wenn sie für eine veränderbare Klasse verwendet wird, um zu verdeutlichen, welche Mitglieder den internen Status des referenzierten Objekts beeinflussen oder nicht.

Für unveränderliche Klassen, die die Mehrheit der Fälle sein sollten (man würde hoffen), denke ich jedoch, dass die Verwendung der Vergangenheitsform die Syntax unnötig umständlich machen kann.

Bei "fließenden Syntaxen", wie sie beispielsweise durch Methodenverkettung erstellt werden, würde die Verwendung der Vergangenheitsform den Code weniger lesbar machen. Wenn Sie beispielsweise LINQ über C # verwenden, kann eine typische Methodenkette wie folgt aussehen:

dataSource.Sort().Take(10).Select(x => x.ToString);

Wenn in die Vergangenheitsform geändert würde, würde dies unangenehm werden:

dataSource.Sorted().Taken(10).Selected(x => x.ToString);

Allerdings hat dataSource.Sorted () eine klarere Bedeutung als dataSource.Sort (), da LINQ-Ausdrücke immer eine neue Aufzählung erstellen, ohne die Quelle zu beeinflussen. Da wir uns dessen jedoch bei der Verwendung dieses Paradigmas voll bewusst sind, ist die Verwendung der Vergangenheitsform überflüssig und macht die "Fließfähigkeit" des Lesens umständlich, ohne dass ein wirklicher Gewinn erzielt wird.

Verwendung von Eigenschaften vs. Methoden

Ein zentrales Thema, das im Folgenden angesprochen wurde, betrifft die FxCop-Richtlinien für die Verwendung von "Get" -Methoden im Vergleich zu schreibgeschützten Eigenschaften. Beispiel: FxCop-Regel CA1024 .

Obwohl in dieser FxCop-Regel nicht explizit erwähnt, scheint die Verwendung von "To" -Methoden auch für diese Begründung zu gelten, da ToXXXXXX () -Methoden Konvertierungsmethoden sind. Im Fall von ToArray () ist die Methode sowohl eine Konvertierungsmethode als auch eine Methode, die ein Array zurückgibt.

Verwendung der Vergangenheitsform für boolesche Eigenschaften

Einige schlugen vor, dass die Vergangenheitsform einen booleschen Wert bedeuten kann, z. B. eine Eigenschaft 'Erfolgreich' oder 'Verbunden'. Ich glaube, dass die folgenden Retorten, in denen die Verwendung von 'Is', 'Has' oder 'Are' für boolesche Eigenschaften und Felder hervorgehoben wird, korrekt sind. Vergangenheitsform kann auch in dieser Hinsicht hilfreich sein, aber bei weitem nicht so viel wie die Verwendung eines 'Is'-Präfixes. Fühlen Sie sich frei, die Vergangenheitsform für boolesche Eigenschaften und Felder zu verwenden, es hilft, aber ich denke, dass es noch wichtiger ist, dem Namen 'Is', 'Has' oder 'Are' voranzustellen.

Allgemeine Schlussfolgerungen?

Für das Problem von Tranpose () vs. Transposed () denke ich, dass 'skizzy' es richtig gemacht hat, was darauf hindeutet, dass es Transpose () für eine mutierende Aktion vs. GetTransposed () für ein nicht mutierendes Ergebnis sein sollte. Dies macht es 100% klar. Aber ich denke auch hier, dass dies möglicherweise nur für eine veränderbare Klasse wie ein Array gilt.

Für unveränderliche Klassen, insbesondere wenn eine fließende Syntax zur Verkettung von Methoden verwendet werden könnte, würde diese Syntax meiner Meinung nach unnötig umständlich werden. Vergleichen Sie zum Beispiel:

var v = myObject.Transpose().Resize(3,5).Shift(2);

zu:

var v = myObject.GetTransposed().GetResized(3,5).GetShifted(2);

Hmmm ... eigentlich scheint es auch hier 100% klar zu sein, dass das Ergebnis die Quelle nicht beeinflusst, aber ich bin mir nicht sicher.

Ich denke, für ein Paradigma wie LINQ, bei dem es bekannt ist, ist die Verwendung von "Get" und / oder der Vergangenheitsform nicht erforderlich, aber für eine neue API könnte es einen gewissen Wert haben - zunächst! Wenn die API nur unveränderliche Objekte enthält, verringert die Verwendung von "Get" und / oder der Vergangenheitsform nur die Lesbarkeit, sobald der Benutzer dies versteht.

Ich denke, hier gibt es keine einfachen Antworten. Ich denke, man muss über die Gesamtheit Ihrer API nachdenken und sorgfältig auswählen!

-- Mike

Mike Rosenblum
quelle
Ich hoffe, Sie vergessen einfach die Parens für den Methodenaufruf (oder diese sind in der Sprache, in der die Beispiele verwendet werden, optional, obwohl dies selten vorkommt), und Sie haben nicht beabsichtigt, dass dies Eigenschaften sind ...
Hallo Delnan, ja, es ist eine Eigenschaft und es ist intensiv. Als Eigenschaft ist es noch deutlicher, dass die Eigenschaft 'Transponieren' (oder 'Transponiert') einen Wert zurückgibt und keine Nebenwirkungen haben sollte. Tatsächlich sollte der Compiler den oben genannten falschen Code verwenden, aber hier geht es darum, dem Benutzer noch klarer zu werden - bevor der Compiler ihn aufgreift.
Mike Rosenblum
Ich denke, es ist eine sehr schlechte Idee, Eigenschaften für alles zu verwenden, um ein völlig neues Objekt zu erstellen. Um dies zu sichern , sagt MSDN, dass eine Methode einer Eigenschaft vorzuziehen ist, wenn "die Methode eine zeitaufwändige Operation ausführt. Die Methode ist spürbar langsamer als die Zeit, die zum Festlegen oder Abrufen des Feldwerts benötigt wird."
Schöne Quelle, vielen Dank dafür. Mein 'Transposed'-Beispiel könnte also ideal gewesen sein, da es über die FxCop-Regel CA1024 ausgelöst wird, da dieses Mitglied (a) eine Konvertierung durchführt und (b) ein Array zurückgibt. Das heißt, die Diskussion über Gegenwart und Vergangenheitsform gilt immer noch, aber ich werde definitiv ToXXXXX () - oder GetXXXX () -Methoden anstelle von Eigenschaften verwenden, wenn ich ein Konvertierungs- oder Array-Ergebnis zurückgebe!
Mike Rosenblum
Ich denke, Sie meinen, Objekte sollten unveränderlich sein, keine Klassen. Klassen sind im Allgemeinen immer unveränderlich, zumindest in statisch typisierten Sprachen.
Nemanja Trifunovic

Antworten:

7

Vergangenheitsform klingt irgendwie unangenehm. Ich denke, Sie wollten, dass das Wort ein Adjektiv ist.

Hier ist mein Vorschlag für das Problem, nach dem Sie gefragt haben: Transpose()Rufen Sie es stattdessen an GetTransposed(). Auf diese Weise wissen Sie, dass Sie etwas empfangen und das Objekt nicht ändern.

Sal
quelle
1
Ich denke, dass dies letztendlich die "richtige" Antwort ist. Ich werde es nicht als "richtig" markieren, weil (1) einige andere, insbesondere "qes" und "Developer Art", einige wirklich nachdenkliche Kommentare abgegeben haben und (2) ich nicht wirklich denke, dass es ein "Recht" gibt. Antwort darauf - dies ist eher eine "Community Wiki" -Frage. (Hat p.se ein solches Konzept?)
Mike Rosenblum
1
+1 Schöne prägnante Antwort! Dieser Weg Transpose()kann immer noch als Anpassen des gegebenen Objekts interpretiert werden. Vielleicht sollte dies ein Sprachkonstrukt sein. :) object.Transpose()vs object:Transpose().
Steven Jeuris
Ich bevorzuge AsTransposedeher als GetTransposedfür Fälle, in denen aufeinanderfolgende wiederholte Anrufe ohne Nebenwirkungen zu semantisch identischen Ergebnissen führen (die möglicherweise dasselbe Objekt sind oder nicht, aber nicht als unterschiedliche Objekte gezeigt werden können, ohne Dinge wie ReferenceEqualsoder das Äquivalent zu verwenden). .
Supercat
6

Es macht in der Tat Sinn.

Was Sie meinen, sollte meiner Meinung nach beim Entwerfen einer API oder einer Bibliothek als allgemeine Regel angesehen werden. Nur dann würde es einen greifbaren Vorteil bieten, wenn das System in allen Klassen konsistent ist.

Ein weiteres Problem ist, dass Benutzer der Schnittstellen in vielen Fällen nicht genau wissen, wie das Ergebnis erzielt wird.

Wird Transponieren () das aktuelle Objekt transponieren? Wird Transpose () eine neue transponierte Instanz erstellen und den Verweis darauf zurückgeben? Oder wird es vielleicht einmal transponiert, die Ergebnisse in einer Kopie zwischengespeichert und bei nachfolgenden Aufrufen zurückgegeben, ohne das aktuelle Objekt zu beeinflussen?

Benutzer möchten oft nur das transponierte Ergebnis erhalten, weshalb sich nicht viele für die von Ihnen vorgeschlagene Namenskonvention interessieren.

Aber für sich genommen ist es ein gültiger Vorschlag.


quelle
4

Ich bin absolut anderer Meinung. Vergangenheitsform ist umständlich und wird nicht häufig verwendet (was Grund genug ist, sie nicht zu bevorzugen).

Wenn die Methode das Objekt geändert hat, für das sie aufgerufen wurde, sollte sie sich nicht selbst zurückgeben. Dies macht deutlich, dass sie das Objekt ändert.

Wenn die Methode das aufgerufene Objekt nicht ändert und stattdessen ein neues Objekt erstellt, gibt die Methode ein Objekt desselben Typs zurück. Dies ist eine klare und bereits übliche Konvention.

Die Methodensignatur ist meiner Meinung nach eine klarere und viel häufiger verwendete Konvention, um anzugeben, ob die Methode ein neues Objekt zurückgibt oder den Angerufenen ändert.

Quentin-Starin
quelle
Hallo Qus, ja, der Vergangenheitsansatz wird nicht häufig verwendet, weshalb ich hier die Frage gestellt habe. (Ich denke, es ist sinnvoll, aber Präzedenzfälle zählen sehr viel.) Ihr Punkt bezüglich der Methodensignatur ist gut, aber leider äußerst subtil, da Compiler im Allgemeinen nicht erzwingen, dass das Ergebnis einer Methode verwendet wird . Ein Compiler konnte also den Fehler nicht auffangen, myObject.Resize () aufzurufen und den Rückgabewert nicht zu verwenden. Mein Vorschlag ist, dass die Benennung der Methode "Resized" dem Programmierer klarer machen würde, dass result = myObject.Resized () erforderlich ist.
Mike Rosenblum
1
Compiler können mit Sicherheit warnen, dass das Ergebnis einer Methode nicht verwendet wird. Dies ist mehr als bei jedem Verhalten, das auf der Zeitform eines Verbs in der englischen Sprache basiert, die in einem Bezeichner verwendet wird. Dies ist also ein ungültiger Punkt. Ich behaupte, dass die Benennung in der Vergangenheitsform es nicht klarer machen wird, weil: 1. es keine etablierte Konvention ist. 2. Es gibt andere etabliertere Konventionen. 3. Es wird viele Wörter geben, die in der Vergangenheitsform außerordentlich umständlich oder völlig ungültig sind, während Methodensignaturen diese Verwirrung nicht erleiden.
Quentin-Starin
Sicher, Compiler könnten davor warnen, aber dies tun sie im Allgemeinen nur für Eigenschaften. Rückgabewerte von Methoden, die nicht verwendet werden, werden von fast allen Compilern in den Standardeinstellungen im Allgemeinen ignoriert.
Mike Rosenblum
Je mehr ich über Ihre Kommentare und die FxCop-Regel CA1024 (msdn.microsoft.com/en-us/library/ms182181(VS.80).aspx) nachdenke, desto mehr Beispiele sind Situationen, in denen eine Konvertierung stattfindet und / oder wo ein Array zurückgegeben wird. Das Ergebnis ist, dass diese eine GetXXXXX () - oder ToXXXXX () -Methodenbezeichnung verwenden sollten, und ich denke, dass dies richtig ist. (Zumal es sich um die etablierte Konvention handelt.) Ich denke, dass die Benennung der Methode in der Passform wie in "GetTransposed ()" hier immer noch hilfreich ist, aber nur am Rande.
Mike Rosenblum
3

Klingt vernünftig und es gibt Präzedenzfälle - Python hat beides list.sort()und sorted(seq). Die erste ist eine Methode, die andere ist eine Funktion, die auf jede iterierbare Methode einwirkt und eine Liste zurückgibt.

Adam Byrtek
quelle
Gibt list.sort () etwas zurück?
Quentin-Starin
Nein, das tut es nicht.
Adam Byrtek
2

In der Scala-Sammlungsbibliothek gibt es einige Methoden in der Vergangenheitsform:

  • groupedGibt einen Iterator zurück, der die Elemente einer Sequenz in Blöcken von nElementen durchläuft
  • sorted Gibt eine sortierte Version der Sammlung zurück
  • updated Gibt eine neue Version der Sammlung mit einem geänderten Element zurück

Allerdings bin ich nicht sicher , ob dies eine bewusste Entscheidung oder ob sie einfach von Namen liefen, weil Scala Sammlung Bibliothek ist sehr reich. Zum Beispiel gibt es auch groupByund sortByMethoden, und die updateMethode ist speziell, weil der Compiler die Zuweisung neu schreibt

foo(bar) = baz

in

foo.update(bar, baz)

Ältere Versionen von Scala in der Tat haben die Verwendung updatefür unveränderlich Sammlungen, aber mit updateeiner neuen Version Rückkehr statt zu mutieren , ist sehr umständlich, weil man offensichtlich den Rückgabewert etwas zuweisen müssen, so zB „Aktualisierung“ ein Eintrag in einer unveränderlichen Karte wäre etwas , mögen

val newMap = oldMap(key) = value // HUH ??!!??

Jetzt ist es

val newMap = oldMap.updated(key -> value)

In einer Sprache mit einem guten Typsystem sagen Ihnen die Typen normalerweise, ob die Routine ihre Argumente mutiert oder nicht. In Haskell zum Beispiel wäre der Typ der unveränderlichen Version so etwas wie

transpose :: Array -> Array

während eine veränderbare Version wäre

transpose :: State Array
Jörg W Mittag
quelle
1

Wenn ich eine Methode / Eigenschaft in der übergebenen Zeit sehe, gehe ich sofort davon aus, dass sie einen Booleschen Wert zurückgibt. Ihr Vorschlag mag logisch sinnvoll sein, aber er scheint nur seltsam und umständlich. Wenn ich jemandem erklären muss, warum es Sinn macht, ist es wahrscheinlich keine gute Konvention.

Ed S.
quelle
2
Aus diesem Grund ist es eine gute Konvention, booleschen Werten "is" voranzustellen. ; p Außerdem erklärt er es, weil es keine Konvention ist. Einige Antworten auf diese Frage stützen sich zu sehr auf bestehende Konventionen. Sie müssen in der Lage sein, beiseite zu treten und es ohne eine bekannte Konvention zu beurteilen.
Steven Jeuris
0

Ich bin kein großer Fan von Variablennamen in der Vergangenheitsform für Unveränderlichkeit - es ist eine etwas ungewöhnliche Konvention und ich denke, sie würde Verwirrung stiften, insbesondere für Personen, die nicht besonders gut in englischer Grammatik sind (und ich mache nur einen halben Scherz) Dort...)

Diese Konventionen beseitigen auch die Möglichkeit, Vergangenheitsform zu verwenden, um etwas anderes zu bezeichnen, was ich für wichtiger halte: Eine Beschreibung des Zustands des beschriebenen Objekts, z. B. "verarbeitete Elemente", zeigt deutlich, dass die Elemente in einer Sammlung bereits vorhanden waren verarbeitet, was wichtige logische Informationen zu vermitteln ist.

Eine bessere Idee: alles unveränderlich machen . Dann brauchen Sie keine ausgefallene sprachliche Unterscheidung. Du willst es doch auch :-)

mikera
quelle
1
Aus diesem Grund ist es eine gute Konvention, booleschen Werten "is" und "has" voranzustellen.
Steven Jeuris