Reicht es aus, wenn Methoden nur nach dem Argumentnamen (nicht nach dem Typ) unterschieden werden?

36

Reicht es aus, Methoden nur nach dem Argumentnamen (nicht nach dem Typ) zu unterscheiden, oder ist es besser, ihn expliziter zu benennen?

Zum Beispiel T Find<T>(int id)vs T FindById<T>(int id).

Gibt es einen guten Grund, es expliziter zu benennen (dh hinzuzufügen ById), anstatt nur den Argumentnamen beizubehalten?

Ein Grund, an den ich denken kann, ist, dass die Signaturen der Methoden gleich sind, aber eine andere Bedeutung haben.

FindByFirstName(string name) und FindByLastName(string name)

Konrad
quelle
4
Also, wenn Sie Find to Include überladen T Find<T>(string name)oder (int size)wie planen Sie, die unvermeidlichen Probleme zu lösen?
UKMonkey
3
@ UKMonkey was unvermeidliche Probleme?
Konrad
3
im ersten Fall: Wenn mehrere Einträge denselben Namen haben, müssten Sie die Funktionssignatur ändern; was bedeutet, dass die Leute wahrscheinlich verwechselt werden mit dem, was zurückkehren soll; Im letzteren Fall ist das Argument dasselbe - und damit eine illegale Überladung. Sie beginnen entweder damit, die Funktion mit "byX" zu benennen, oder Sie erstellen ein Objekt für das Argument, sodass Sie das Äquivalent einer Überladung mit demselben Argument erhalten. Entweder funktioniert gut für verschiedene Situationen.
UKMonkey
2
@ UKMonkey Sie können eine Antwort mit einigen Codebeispielen posten, wenn Sie möchten
Konrad
3
IDs sollten wahrscheinlich ein undurchsichtiges IDObjekt sein und nicht nur ein int. Auf diese Weise überprüfen Sie während der Kompilierung, ob Sie in einem Teil Ihres Codes eine ID für ein int oder umgekehrt verwenden. Und damit kannst du find(int value)und haben find(ID id).
Bakuriu

Antworten:

68

Sicher gibt es einen guten Grund, es expliziter zu nennen.

Es ist nicht in erster Linie die Methode Definition , die selbsterklärend sein sollte, aber die Methode verwenden . Und während findById(string id)und find(string id)beide selbsterklärend sind, gibt es einen großen Unterschied zwischen findById("BOB")und find("BOB"). Im ersteren Fall wissen Sie, dass das zufällige Literal tatsächlich eine ID ist. Im letzteren Fall sind Sie sich nicht sicher - es könnte sich tatsächlich um einen Vornamen oder etwas ganz anderes handeln.

Kilian Foth
quelle
9
Außer in dem 99% der anderen Fälle , in denen Sie haben eine Variable oder Eigenschaftsnamen ein Bezugspunkt sind: findById(id)vs find(id). Sie können so oder so vorgehen.
Greg Burghardt
16
@GregBurghardt: Ein bestimmter Wert wird für eine Methode und ihren Aufrufer nicht unbedingt gleich benannt. Betrachten wir zum Beispiel double Divide(int numerator, int denominator)in einem Verfahren verwendet werden: double murdersPerCapita = Divide(murderCount, citizenCount). Sie können sich nicht auf zwei Methoden verlassen, die denselben Variablennamen verwenden, da es viele Fälle gibt, in denen dies nicht der Fall ist (oder wenn dies der Fall ist, ist es eine falsche Benennung)
Flater
1
@Flater: Angesichts des Codes in der Frage (das Auffinden von Daten aus einem dauerhaften Speicher) könnte man diese Methode mit "murderedCitizenId" oder "citizenId" bezeichnen mehrdeutig hier. Und ehrlich gesagt könnte ich so oder so vorgehen. Es ist eigentlich eine sehr aufgeschlossene Frage.
Greg Burghardt
4
@ GregBurghardt: Sie können eine globale Regel nicht anhand eines einzelnen Beispiels destillieren. Die Frage von OP ist im Allgemeinen nicht spezifisch für das gegebene Beispiel. Ja, es gibt Fälle, in denen die Verwendung des gleichen Namens sinnvoll ist, aber es gibt auch Fälle, in denen dies nicht der Fall ist. Aus diesem Grund ist es am besten, auf Konsistenz zu achten, auch wenn dies in einer Untergruppe von Anwendungsfällen nicht erforderlich ist.
Flater
1
Namensparameter beheben die Verwirrung, nachdem die Verwirrung bereits vorhanden war , da Sie die Methodendefinition betrachten müssen, während explizit benannte Methoden die Verwirrung vollständig vermeiden, indem der Name im Methodenaufruf vorhanden ist. Außerdem können Sie nicht zwei Methoden mit demselben Namen und Argumenttyp, aber unterschiedlichem Argumentnamen verwenden, was bedeutet, dass Sie in bestimmten Fällen ohnehin explizite Namen benötigen.
Darkhogg
36

Vorteile von FindById () .

  1. Zukunftssicherheit : Wenn Sie mit beginnen Find(int), und später anderen Methoden hinzufügen müssen ( FindByName(string), FindByLegacyId(int), FindByCustomerId(int), FindByOrderId(int)usw.), Leute wie ich sind in der Regel im Alter zu verbringen suchen FindById(int). Nicht wirklich ein Problem , wenn Sie können und wird sich ändern Find(int)zu , FindById(int)sobald es notwendig wird - Zukunftssicherheit ist über diese , wenn s.

  2. Einfacher zu lesen . Findist vollkommen in Ordnung, wenn der Anruf so aussieht. Ist jedoch etwas einfacher zu lesen, wenn dies der record = Find(customerId);Fall FindByIdist record = FindById(AFunction());.

  3. Konsistenz . Sie können das Muster FindByX(int)/ FindByY(int)überall konsistent anwenden , Find(int X)/ Find(int Y)ist jedoch nicht möglich, da Konflikte auftreten.

Vorteile von Find ()

  • KUSS. Findist einfach und unkompliziert und nebenbei operator[]einer der 2 am meisten erwarteten Funktionsnamen in diesem Zusammenhang. (Einige beliebte Alternativen sind get, lookupoder fetch, je nach Kontext).
  • Als Faustregel gilt: Wenn Sie einen Funktionsnamen haben, der ein bekanntes Wort ist, das genau beschreibt, was die Funktion tut, verwenden Sie ihn. Auch wenn es einen längeren Namen mit mehreren Wörtern gibt, der die Funktionsweise der Funktion etwas besser beschreibt. Beispiel: Länge vs NumberOfElements . Es gibt einen Kompromiss, und die Frage, wo die Grenze gezogen werden soll, ist Gegenstand einer laufenden Debatte.
  • Es ist im Allgemeinen gut, Redundanzen zu vermeiden. Wenn wir uns das ansehen FindById(int id), können wir Redundanzen leicht entfernen, indem wir sie auf ändern Find(int id), aber es gibt einen Kompromiss - wir verlieren etwas Klarheit.

Alternativ können Sie die Vorteile von beiden nutzen, indem Sie stark typisierte IDs verwenden:

CustomerRecord Find(Id<Customer> id) 
// Or, depending on local coding standards
CustomerRecord Find(CustomerId id) 

Implementierung von Id<>: Starke Eingabe von ID-Werten in C #

Die Kommentare hier und im obigen Link haben zu mehreren Bedenken geführt, auf Id<Customer>die ich eingehen möchte:

  • Bedenken 1: Es ist ein Missbrauch von Generika. CustomerIdund OrderIDsind verschiedene Typen ( customerId1 = customerId2;=> gut, customerId1 = orderId1;=> schlecht), aber ihre Implementierung ist nahezu identisch, sodass wir sie entweder mit Copy Paste oder mit Metaprogramm implementieren können. Während in einer Diskussion Wert darauf gelegt wird, das Generikum entweder freizulegen oder zu verbergen, ist Metaprogrammierung das, wofür Generika gedacht sind.
  • Bedenken 2: Es hört nicht mit einfachen Fehlern auf./Es ist eine Lösung auf der Suche nach einem Problem. Das Hauptproblem, das durch die Verwendung stark typisierter IDs behoben wird, ist die falsche Argumentreihenfolge in einem Aufruf an DoSomething(int customerId, int orderId, int productId). Stark eingegebene IDs verhindern auch andere Probleme, einschließlich desjenigen, nach dem OP gefragt wurde.
  • Bedenken 3: Es verdeckt wirklich nur den Code. Es ist schwer zu sagen, ob ein Ausweis festgehalten wird int aVariable. Es ist leicht zu erkennen, dass eine ID gespeichert ist Id<Customer> aVariable, und wir können sogar feststellen, dass es sich um eine Kunden-ID handelt.
  • Bedenken 4: Diese IDs sind keine starken Typen, nur Wrapper. Stringist nur ein Wrapper herum byte[]. Das Umschließen oder Einkapseln steht nicht im Widerspruch zu starker Typisierung.
  • Bedenken 5: Es ist ausgereift. Hier ist die Minimalversion, obwohl ich das Hinzufügen empfehle operator==und operator!=auch, wenn Sie sich nicht ausschließlich auf Folgendes verlassen möchten Equals:

.

public struct Id<T>: {
    private readonly int _value ;
    public Id(int value) { _value = value; }
    public static explicit operator int(Id<T> id) { return id._value; }
}
Peter
quelle
10

Eine andere Art, darüber nachzudenken, besteht darin, die Typensicherheit der Sprache zu verwenden.

Sie können eine Methode wie die folgenden implementieren:

Find(FirstName name);

Wobei FirstName ein einfaches Objekt ist, das eine Zeichenfolge umschließt, die den Vornamen enthält und bedeutet, dass es keine Verwirrung darüber geben kann, was die Methode tut und mit welchen Argumenten sie aufgerufen wird.

3DPrintScanner
quelle
4
Sie sind sich nicht sicher, wie Ihre Antwort auf die Frage des OP lautet. Empfehlen Sie, einen Namen wie "Suchen" zu verwenden, indem Sie sich auf die Art des Arguments verlassen? Oder empfehlen Sie, solche Namen nur zu verwenden, wenn es einen expliziten Typ für die Argumente gibt, und an anderer Stelle einen expliziten Namen wie "FindById" zu verwenden? Oder empfehlen Sie explizite Typen einzuführen, um einen Namen wie "Find" praktikabler zu machen?
Doc Brown
2
@ DocBrown Ich denke letzteres, und ich mag es. Es ist tatsächlich ähnlich wie Peters Antwort, iiuc. Nach meinem Verständnis gibt es zwei Gründe: (1) Aus dem Argumenttyp wird klar, was die Funktion tut. (2) Sie können keine Fehler wie. string name = "Smith"; findById(name);Machen, die möglich sind, wenn Sie nicht beschreibende allgemeine Typen verwenden.
Peter - Wiedereinsetzung von Monica
5
Im Allgemeinen bin ich ein Fan der Sicherheit beim Kompilieren. Seien Sie jedoch vorsichtig mit Wrapper-Typen. Das Einführen von Wrapper-Klassen aus Gründen der Typensicherheit kann Ihre API manchmal erheblich komplizieren, wenn dies übermäßig erfolgt. zB das ganze "artist formerly known as int" -Problem, das Winapi hat; Auf lange Sicht würde ich sagen, die meisten Leute schauen sich nur die endlosen DWORD LPCSTRKlone usw. an und denken, "es ist ein int / string / etc.", es kommt zu dem Punkt, an dem Sie mehr Zeit damit verbringen, Ihre Werkzeuge zu stützen, als Sie tatsächlich Code entwerfen .
Jrh
1
@jrh Richtig. Mein Lackmustest zur Einführung von "nominalen" Typen (die sich nur im Namen unterscheiden) ist, wenn allgemeine Funktionen / Methoden in unserem Anwendungsfall keinen Sinn ergeben, z. B. werden ints häufig summiert, multipliziert usw., was für IDs bedeutungslos ist. also würde ich mich IDunterscheiden von int. Dies kann eine API vereinfachen, indem eingegrenzt wird, was wir mit einem bestimmten Wert tun können (z. B. wenn wir einen haben ID, funktioniert dies nur mit find, nicht z . B. ageoder highscore). Wenn wir dagegen feststellen, dass wir viel konvertieren oder dieselbe Funktion / Methode (z. B. find) für mehrere Typen schreiben , ist dies ein Zeichen dafür, dass unsere Unterscheidungen zu fein sind
Warbo,
1

Ich werde für eine explizite Erklärung wie FindByID stimmen. Software sollte für den Wandel gebaut werden. Es sollte offen und geschlossen sein (SOLID). Die Klasse ist also offen, ähnliche Suchmethoden wie FindByName usw. hinzuzufügen.

FindByID ist jedoch geschlossen und seine Implementierung ist Unit-getestet.

Ich werde keine Methoden mit Prädikaten vorschlagen, die sind gut im Allgemeinen. Was ist, wenn Sie basierend auf dem Feld (ByID) unterschiedliche Methoden anwenden.

BrightFuture1029
quelle
0

Ich bin überrascht, dass niemand vorgeschlagen hat, ein Prädikat wie das folgende zu verwenden:

User Find(Predicate<User> predicate)

Mit diesem Ansatz reduzieren Sie nicht nur die Oberfläche Ihrer API, sondern geben dem Benutzer, der sie verwendet, auch mehr Kontrolle.

Wenn das nicht ausreicht, können Sie es jederzeit nach Ihren Wünschen erweitern.

Aybe
quelle
1
Das Problem ist, dass es weniger effizient ist, weil es Dinge wie Indizes nicht nutzen kann.
Solomon Ucko