In Onkel Bobs Kapitel über Namen in Clean Code wird empfohlen, Kodierungen in Namen zu vermeiden, vor allem in Bezug auf die ungarische Notation. Er erwähnt auch ausdrücklich das Entfernen des I
Präfixes von den Schnittstellen, zeigt jedoch keine Beispiele dafür.
Nehmen wir folgendes an:
- Die Verwendung der Schnittstelle dient hauptsächlich dazu, Testbarkeit durch Abhängigkeitsinjektion zu erreichen
- In vielen Fällen führt dies zu einer einzigen Schnittstelle mit einem einzigen Implementierer
Wie sollten diese beiden zum Beispiel heißen? Parser
und ConcreteParser
? Parser
und ParserImplementation
?
public interface IParser {
string Parse(string content);
string Parse(FileInfo path);
}
public class Parser : IParser {
// Implementations
}
Oder sollte ich diesen Vorschlag in Einzelfällen wie diesen ignorieren?
c#
naming
clean-code
naming-standards
Vinko Vrsalovic
quelle
quelle
Antworten:
Während viele, einschließlich "Onkel Bob", raten, nicht
I
als Präfix für Schnittstellen zu verwenden, ist dies bei C # eine weit verbreitete Tradition. Im Allgemeinen sollte es vermieden werden. Wenn Sie jedoch C # schreiben, sollten Sie die Konventionen dieser Sprache befolgen und verwenden. Wenn Sie dies nicht tun, kommt es zu großer Verwirrung mit anderen mit C # vertrauten Personen, die versuchen, Ihren Code zu lesen.quelle
Die Schnittstelle ist das wichtige logische Konzept, daher sollte die Schnittstelle den generischen Namen tragen. Also hätte ich lieber
als
Letzteres hat mehrere Probleme:
quelle
Default
oderReal
oderImpl
in so viele meiner KlassennamenIList<T>
und es ist ihm egal, wie sie intern gespeichert wird. Die Möglichkeit, nur ein wenig von demI
zu lernen und einen verwendbaren Klassennamen zu haben, scheint besser zu sein, als magisch zu wissen (wie in Java), welchen Code eine gewöhnliche Implementierung verwendenList<T>
sollArrayList<T>
.CFoo : Foo
. Diese Option ist also nicht wirklich verfügbar. Zwei, erhalten Sie eine seltsame Inkonsistenz dann weil einige Klassen den Marker haben würden und andere nicht, je nachdem , ob es vielleicht andere Implementierungen des gleichen Schnittstelle sein.CFoo : Foo
aberSqlStore : Store
. Das ist ein Problem, mit dem ich zu tun habeImpl
, das im Wesentlichen nur ein hässlicherer Marker ist. Vielleicht, wenn es eine Sprache mit der Konvention gab, immer einC
(oder was auch immer) hinzuzufügen , könnte das besser sein alsI
Hier geht es nicht nur um Namenskonventionen. C # unterstützt keine Mehrfachvererbung, daher bietet diese traditionelle Verwendung der ungarischen Notation einen kleinen, wenn auch nützlichen Vorteil, wenn Sie von einer Basisklasse erben und eine oder mehrere Schnittstellen implementieren. Also das...
... ist dem vorzuziehen ...
IMO
quelle
inherits
vsimplements
.extends
.Nein nein Nein.
Namenskonventionen sind keine Funktion Ihres Onkel Bob-Fandoms. Sie sind keine Funktion der Sprache, c # oder auf andere Weise.
Sie sind eine Funktion Ihrer Codebasis. In viel geringerem Umfang Ihr Geschäft. Mit anderen Worten, der erste in der Codebasis setzt den Standard.
Nur dann können Sie sich entscheiden. Danach einfach konsequent sein. Wenn jemand anfängt inkonsistent zu sein, nimm seine Nerf-Waffe weg und lass sie die Dokumentation aktualisieren, bis sie Buße tun.
Wenn ich beim Lesen einer neuen Codebasis kein
I
Präfix sehe, ist das in Ordnung, unabhängig von der Sprache. Wenn ich auf jeder Schnittstelle eine sehe, geht es mir gut. Wenn ich manchmal einen sehe, wird jemand dafür bezahlen.Wenn Sie sich in dem Kontext befinden, in dem Sie diesen Präzedenzfall für Ihre Codebasis festgelegt haben, sollten Sie Folgendes berücksichtigen:
Als Kundenklasse behalte ich mir das Recht vor, das, womit ich spreche, nicht gleichgültig zu machen. Ich brauche nur einen Namen und eine Liste von Dingen, die ich gegen diesen Namen anrufen kann. Diese dürfen sich nicht ändern, wenn ich es nicht sage. Ich besitze diesen Teil. Was ich spreche, Schnittstelle oder nicht, ist nicht meine Abteilung.
Wenn Sie sich
I
in diesen Namen schleichen , ist es dem Kunden egal. Ist dies nichtI
der Fall, ist es dem Kunden egal. Womit es sich unterhält, könnte konkret sein. Es könnte nicht sein. Da es so oder so nichtnew
darauf ankommt, macht es keinen Unterschied. Als Kunde weiß ich es nicht. Ich will es nicht wissen.Als Code-Affe, der sich diesen Code ansieht, ist dies vielleicht sogar in Java wichtig. Wenn ich eine Implementierung in eine Schnittstelle ändern muss, kann ich das schließlich tun, ohne es dem Client mitzuteilen. Wenn es keine
I
Tradition gibt, werde ich sie vielleicht nicht einmal umbenennen. Das könnte ein Problem sein, denn jetzt müssen wir den Client neu kompilieren, auch wenn es der Quelle egal ist, was die Binärdatei tut. Etwas, das leicht zu übersehen ist, wenn Sie den Namen nie geändert haben.Vielleicht ist das für Sie kein Problem. Vielleicht nicht. Wenn ja, ist das ein guter Grund, sich darum zu kümmern. Aber aus Liebe zu Tischspielzeug sollten wir das nicht einfach blind machen, denn es ist die Art und Weise, wie es in irgendeiner Sprache gemacht wird. Entweder hast du einen verdammt guten Grund oder es ist mir egal.
Es geht nicht darum, für immer damit zu leben. Es geht darum, mich nicht über die Codebasis zu bescheißen, die nicht Ihrer speziellen Mentalität entspricht, es sei denn, Sie sind bereit, das Problem überall zu beheben. Wenn Sie keinen guten Grund haben, den Weg des geringsten Widerstands gegen Gleichförmigkeit nicht zu beschreiten, kommen Sie nicht zu mir und predigen Konformität. Ich habe besseres zu tun.
quelle
I
s sehen. Wenn Ihr Code sie verwendet, sehen Sie sie überall. Wenn Ihr Code sie nicht verwendet, sehen Sie sie im Framework-Code, was genau zu der Mischung führt, die Sie nicht wollen.Es gibt einen winzigen Unterschied zwischen Java und C #, der hier relevant ist. In Java ist standardmäßig jedes Mitglied virtuell. In C # ist jedes Mitglied standardmäßig versiegelt - mit Ausnahme der Mitglieder der Benutzeroberfläche.
Die damit verbundenen Annahmen beeinflussen die Richtlinie - in Java sollte jeder öffentliche Typ gemäß dem Substitutionsprinzip von Liskov als nicht endgültig angesehen werden [1]. Wenn Sie nur eine Implementierung haben, geben Sie der Klasse einen Namen
Parser
. Wenn Sie feststellen, dass Sie mehrere Implementierungen benötigen, ändern Sie die Klasse einfach in eine Schnittstelle mit demselben Namen und benennen die konkrete Implementierung in eine beschreibende Implementierung um.In C # ist die Hauptannahme, dass, wenn Sie eine Klasse erhalten (Name beginnt nicht mit
I
), dies die gewünschte Klasse ist. Wohlgemerkt, dies ist bei weitem nicht 100% genau - ein typisches Gegenbeispiel wären Klassen wieStream
(die eigentlich eine Schnittstelle oder ein paar Schnittstellen sein sollten), und jeder hat seine eigenen Richtlinien und Hintergründe aus anderen Sprachen. Es gibt auch andere Ausnahmen wie das häufig verwendeteBase
Suffix für eine abstrakte Klasse - genau wie bei einer Schnittstelle wissen Sie, dass der Typ polymorph sein soll.Es gibt auch eine nette Usability-Funktion, wenn Sie den Namen ohne I-Präfix für Funktionen belassen, die sich auf diese Schnittstelle beziehen, ohne die Schnittstelle zu einer abstrakten Klasse machen zu müssen (was aufgrund der fehlenden Klassenmehrfachvererbung in C # schaden würde). Dies wurde von LINQ, das
IEnumerable<T>
als Schnittstelle verwendet wird, undEnumerable
als Repository von Methoden, die für diese Schnittstelle gelten, populär gemacht . Dies ist in Java nicht erforderlich, wo Schnittstellen auch Methodenimplementierungen enthalten können.Letztendlich ist das
I
Präfix in der C # -Welt und in der .NET-Welt weit verbreitet (da der weitaus größte Teil des .NET-Codes in C # geschrieben ist, ist es sinnvoll, die C # -Richtlinien für die meisten öffentlichen Schnittstellen zu befolgen). Das bedeutet, dass Sie mit ziemlicher Sicherheit mit Bibliotheken und Code arbeiten werden, die dieser Notation folgen, und es ist sinnvoll, die Tradition zu übernehmen, um unnötige Verwirrung zu vermeiden - es ist nicht so, als würde das Weglassen des Präfixes Ihren Code verbessern :)Ich gehe davon aus, dass Onkel Bobs Argumentation ungefähr so war:
IBanana
ist der abstrakte Begriff der Banane. Wenn es eine implementierende Klasse geben kann, die keinen besseren Namen als hatBanana
, ist die Abstraktion völlig bedeutungslos, und Sie sollten die Schnittstelle löschen und einfach eine Klasse verwenden. Wenn es einen besseren Namen gibt (etwaLongBanana
oderAppleBanana
), gibt es keinen Grund, ihn nichtBanana
als Namen für die Schnittstelle zu verwenden. DaherI
bedeutet die Verwendung des Präfixes, dass Sie eine nutzlose Abstraktion haben, wodurch der Code ohne Nutzen schwerer zu verstehen ist. Und da Sie beim strengen OOP immer Code gegen Schnittstellen verwenden, ist der einzige Ort, an dem Sie dasI
Präfix für einen Typ nicht sehen würden, ein Konstruktor - ziemlich sinnloses Rauschen.Wenn Sie dies auf Ihre Beispielschnittstelle anwenden
IParser
, können Sie deutlich erkennen, dass sich die Abstraktion vollständig im "bedeutungslosen" Bereich befindet. Entweder es ist etwas Bestimmtes über eine konkrete Implementierung eines Parsers ( zum BeispielJsonParser
,XmlParser
, ...), oder sollten Sie nur eine Klasse. Es gibt keine "Standardimplementierung" (obwohl dies in einigen Umgebungen tatsächlich Sinn macht, insbesondere COM), entweder gibt es eine bestimmte Implementierung, oder Sie möchten eine abstrakte Klasse oder Erweiterungsmethoden für die "Standardeinstellungen".I
Behalten Sie es jedoch in C # bei, es sei denn, Ihre Codebasis lässt das Präfix bereits weg. Machen Sie sich einfach jedes Mal eine mentale Notiz, wenn Sie Code wie sehenclass Something: ISomething
- das bedeutet, dass jemand nicht sehr gut darin ist, YAGNI zu folgen und vernünftige Abstraktionen zu erstellen .[1] - Technisch gesehen wird dies in Liskovs Artikel nicht speziell erwähnt, aber es ist eine der Grundlagen des ursprünglichen OOP-Artikels und in meiner Lektüre von Liskov hat sie dies nicht in Frage gestellt. In einer weniger strengen Interpretation (die von den meisten OOP-Sprachen verwendet wird) bedeutet dies, dass jeder Code, der einen öffentlichen Typ verwendet, der ersetzt werden soll (dh nicht endgültig / versiegelt), mit jeder konformen Implementierung dieses Typs funktionieren muss.
quelle
In einer idealen Welt sollten Sie Ihrem Namen keine Abkürzung ("I ...") voranstellen, sondern den Namen einfach ein Konzept ausdrücken lassen.
Ich habe irgendwo gelesen (und kann es nicht als Quelle angeben), dass diese ISomething-Konvention Sie dazu bringt, ein "IDollar" -Konzept zu definieren, wenn Sie eine "Dollar" -Klasse benötigen, aber die richtige Lösung wäre ein Konzept, das einfach "Währung" genannt wird.
Mit anderen Worten, die Konvention gibt der Schwierigkeit, Dinge zu benennen, ein leichtes Ende und das leichte Ende ist auf subtile Weise falsch .
In der realen Welt müssen die Clients Ihres Codes (die möglicherweise nur "Sie von der Zukunft" sind) in der Lage sein, ihn später zu lesen und sich so schnell (oder mit so wenig Aufwand) wie möglich damit vertraut zu machen (geben Sie die Clients von deinem Code, was sie erwarten zu bekommen).
Die ISomething-Konvention führt Cruft / Bad Names ein. Das heißt, die Cruft ist keine echte Cruft *), es sei denn, die Konventionen und Praktiken, die beim Schreiben des Codes verwendet werden, sind nicht mehr aktuell. Wenn in der Konvention "use ISomething" steht, ist es am besten, sie zu verwenden, um sich anderen Entwicklern anzupassen (und besser zu arbeiten).
*) Ich kann mit der Aussage davonkommen, dass "cruft" sowieso kein genaues Konzept ist :)
quelle
Wie in den anderen Antworten erwähnt, ist das Präfixieren Ihrer Schnittstellennamen
I
Teil der Codierungsrichtlinien für .NET Framework. Da in C # und VB.NET mit konkreten Klassen häufiger interagiert wird als mit generischen Schnittstellen, sind sie in Namensschemata erstklassig und sollten daher die einfachsten Namen haben - daherIParser
für die Schnittstelle undParser
für die Standardimplementierung.Bei Java und ähnlichen Sprachen, bei denen Schnittstellen anstelle konkreter Klassen bevorzugt werden, hat Onkel Bob jedoch Recht, da die
I
entfernt werden sollten und die Schnittstellen die einfachsten Namen haben sollten - zum BeispielParser
für Ihre Parser-Schnittstelle. Anstelle eines rollenbasierten NamensParserDefault
sollte die Klasse einen Namen haben, der beschreibt, wie der Parser implementiert wird :Dies ist zum Beispiel, wie
Set<T>
,HashSet<T>
undTreeSet<T>
sind benannt.quelle
sealed
standardmäßig sind und Sie sie explizit festlegen müssenvirtual
. Virtuell zu sein ist der Spezialfall in C #. Da sich der empfohlene Ansatz zum Schreiben von Code im Laufe der Jahre geändert hat, mussten viele Relikte der Vergangenheit aus Kompatibilitätsgründen erhalten bleiben :) Der entscheidende Punkt ist, dassI
Sie wissen, dass eine Schnittstelle in C # (die mit beginnt ) eine vollständige Schnittstelle ist abstrakter Typ; Wenn Sie eine Nicht-Schnittstelle erhalten, ist die typische Annahme, dass dies der Typ ist, den Sie wollen, LSP, verdammt.Sie haben Recht, dass diese I = Interface-Konvention die (neuen) Regeln verletzt
Früher haben Sie alles mit dem Typ intCounter boolFlag usw. vorangestellt.
Wenn Sie Dinge ohne Präfix benennen möchten, aber auch "ConcreteParser" vermeiden möchten, können Sie Namespaces verwenden
quelle
intCounter
oder schreiben würdenboolFlag
. Das sind die falschen "Typen". Stattdessen schreiben SiepszName
(Zeiger auf eine NUL-terminierte Zeichenfolge, die einen Namen enthält),cchName
(Anzahl der Zeichen in der Zeichenfolge "name"),xControl
(x-Koordinate des Steuerelements),fVisible
(Flag, das angibt, dass etwas sichtbar ist),pFile
(Zeiger auf eine Datei),hWindow
(Handle zu einem Fenster) und so weiter.xControl
zucchName
. Sie sind beide vom Typint
, haben aber eine sehr unterschiedliche Semantik. Die Präfixe machen diese Semantik für einen Menschen offensichtlich. Ein Zeiger auf eine mit NUL abgeschlossene Zeichenfolge sieht für einen Compiler genauso aus wie ein Zeiger auf eine Pascal-Zeichenfolge. Ebenso entstand dasI
Präfix für Interfaces in der Sprache C ++, in der Interfaces eigentlich nur Klassen sind (und in der Tat ist C, in dem es keine Klasse oder Schnittstelle auf Sprachebene gibt, alles nur eine Konvention).