it-swarm.com.de

Namensprobleme: Sollte "ISomething" in "Something" umbenannt werden?

In Onkel Bobs Kapitel über Namen in Clean Code wird empfohlen, Kodierungen in Namen zu vermeiden, hauptsächlich in Bezug auf die ungarische Notation. Er erwähnt auch ausdrücklich das Entfernen des Präfixes I von Schnittstellen, zeigt jedoch keine Beispiele dafür.

Nehmen wir Folgendes an:

  • Die Verwendung der Schnittstelle dient hauptsächlich dazu, die Testbarkeit durch Abhängigkeitsinjektion zu erreichen
  • In vielen Fällen führt dies zu einer einzigen Schnittstelle mit einem einzigen Implementierer

Wie sollen diese beiden beispielsweise benannt werden? 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 solchen Fällen mit einer einzelnen Implementierung ignorieren?

68
Vinko Vrsalovic

Während viele, einschließlich "Onkel Bob", davon abraten, I als Präfix für Schnittstellen zu verwenden, ist dies bei C # eine etablierte 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, wird dies zu großen Verwirrungen mit anderen Personen führen, die mit C # vertraut sind und versuchen, Ihren Code zu lesen.

188
David Arno

Die Schnittstelle ist das wichtige logische Konzept, daher sollte die Schnittstelle den generischen Namen tragen. Also hätte ich lieber

interface Something
class DefaultSomething : Something
class MockSomething : Something

als

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Letzteres hat mehrere Probleme:

  1. Etwas ist nur eine Implementierung von ISomething, aber es ist die mit dem generischen Namen, als wäre es irgendwie etwas Besonderes.
  2. MockSomething scheint von Something abzuleiten, implementiert jedoch ISomething. Vielleicht sollte es MockISomething heißen, aber das habe ich in freier Wildbahn noch nie gesehen.
  3. Refactoring ist schwieriger. Wenn Something jetzt eine Klasse ist und Sie erst später herausfinden, dass Sie eine Schnittstelle einführen müssen, sollte diese Schnittstelle den gleichen Namen haben, da sie für Clients transparent sein sollte, ob der Typ eine konkrete Klasse, eine abstrakte Klasse oder eine Schnittstelle ist . Etwas bricht das.
39
wallenborn

Hier geht es nicht nur um Namenskonventionen. C # unterstützt keine Mehrfachvererbung, daher hat diese ältere 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...

class Foo : BarB, IBarA
{
    // Better...
}

... ist diesem vorzuziehen ...

class Foo : BarB, BarA
{
    // Dafuq?
}

IMO

27
Robbie Dee

Nein nein Nein.

Namenskonventionen sind keine Funktion Ihres Onkel Bob-Fandoms. Sie sind keine Funktion der Sprache, c # oder anders.

Sie sind eine Funktion Ihrer Codebasis. In viel geringerem Maße von Ihrem Geschäft. Mit anderen Worten, der erste in der Codebasis setzt den Standard.

Erst dann können Sie sich entscheiden. Danach sei einfach konsequent. Wenn jemand inkonsistent wird, nehmen Sie seine Nerf-Waffe weg und lassen Sie ihn die Dokumentation aktualisieren, bis er Buße tut.

Wenn ich beim Lesen einer neuen Codebasis nie ein Präfix I sehe, geht es mir gut, unabhängig von der Sprache. Wenn ich auf jeder Schnittstelle eine sehe, geht es mir gut. Wenn ich manchmal einen sehe, wird jemand bezahlen.

Wenn Sie sich in dem verdünnten Kontext befinden, diesen Präzedenzfall für Ihre Codebasis festzulegen, sollten Sie Folgendes berücksichtigen:

Als Kundenklasse behalte ich mir das Recht vor, mir egal zu sein, womit ich spreche. Ich brauche nur einen Namen und eine Liste von Dingen, die ich gegen diesen Namen aufrufen 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 ein I in diesen Namen schleichen, ist es dem Client egal. Wenn es kein I gibt, ist es dem Client egal. Wovon es spricht, könnte konkret sein. Es könnte nicht. Da es auf keinen Fall new aufruft, macht es keinen Unterschied. Als Kunde weiß ich es nicht. Ich will es nicht wissen.

Als Code-Affe, der diesen Code auch in Java betrachtet, könnte dies wichtig sein. Wenn ich eine Implementierung in eine Schnittstelle ändern muss, kann ich das tun, ohne es dem Client mitzuteilen. Wenn es keine I Tradition gibt, kann ich sie nicht einmal umbenennen. Das könnte ein Problem sein, weil wir jetzt den Client neu kompilieren müssen, denn obwohl es der Quelle egal ist, tut dies die Binärdatei. 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 Schreibtischspielzeug behaupten wir nicht, wir sollten dies einfach blind tun, weil es so ist, wie es in einer gewissen Sprache gemacht wird. Entweder haben Sie einen verdammt guten Grund oder es ist Ihnen egal.

Es geht nicht darum, für immer damit zu leben. Es geht darum, mir keinen Mist über die Codebasis zu geben, die nicht Ihrer speziellen Mentalität entspricht, es sei denn, Sie sind bereit, das "Problem" überall zu beheben. Wenn Sie keinen guten Grund haben, warum ich nicht den Weg des geringsten Widerstands gegen die Einheitlichkeit beschreiten sollte, kommen Sie nicht zu mir, um Konformität zu predigen. Ich habe besseres zu tun.

15
candied_orange

Es gibt einen winzigen Unterschied zwischen Java und C #, der hier relevant ist. In Java ist jedes Mitglied standardmäßig virtuell. In C # ist jedes Mitglied standardmäßig versiegelt - mit Ausnahme der Schnittstellenmitglieder.

Die damit verbundenen Annahmen beeinflussen die Richtlinie - in Java sollte jeder öffentliche Typ gemäß dem Substitutionsprinzip von Liskov [1] als nicht endgültig betrachtet werden. Wenn Sie nur eine Implementierung haben, benennen Sie die Klasse 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 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 wie Stream (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 ziemlich häufig verwendete Suffix Base, um eine abstrakte Klasse zu bezeichnen - genau wie bei einer Schnittstelle soll wissen der Typ polymorph sein.

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 Mehrfachvererbung von Klassen in C # schaden würde). Dies wurde von LINQ populär gemacht, das IEnumerable<T> Als Schnittstelle und Enumerable als Repository für Methoden verwendet, die für diese Schnittstelle gelten. Dies ist in Java nicht erforderlich, wo Schnittstellen auch Methodenimplementierungen enthalten können.

Letztendlich ist das Präfix I in der C # -Welt und in der .NET-Welt weit verbreitet (da der größte Teil des .NET-Codes in C # geschrieben ist, ist es sinnvoll, die C # -Richtlinien für die meisten zu befolgen die öffentlichen Schnittstellen). Dies 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 nehme an, 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 Banana hätte, ist die Abstraktion völlig bedeutungslos, und Sie sollten die Schnittstelle löschen und einfach eine Klasse verwenden. Wenn es einen besseren Namen gibt (z. B. LongBanana oder AppleBanana), gibt es keinen Grund, Banana nicht als Namen der Schnittstelle zu verwenden. Daher bedeutet die Verwendung des Präfixes I, dass Sie eine nutzlose Abstraktion haben, wodurch das Verstehen des Codes ohne Nutzen schwieriger wird. Und da Sie mit strict OOP) immer gegen Schnittstellen codieren, ist der einzige Ort, an dem Sie das Präfix I für einen Typ nicht sehen würden, ein Konstruktor - ziemlich sinnloses Rauschen .

Wenn Sie dies auf Ihre Beispielschnittstelle IParser anwenden, können Sie deutlich erkennen, dass sich die Abstraktion vollständig im "bedeutungslosen" Gebiet befindet. Entweder hat eine konkrete Implementierung eines Parsers etwas Spezifisches (z. B. JsonParser, XmlParser, ...), oder Sie sollten einfach eine Klasse verwenden. Es gibt keine "Standardimplementierung" (obwohl dies in einigen Umgebungen tatsächlich sinnvoll ist - insbesondere COM), entweder gibt es eine bestimmte Implementierung oder Sie möchten eine abstrakte Klasse oder Erweiterungsmethoden für die "Standardeinstellungen". Behalten Sie es jedoch in C # bei, es sei denn, Ihre Codebasis lässt das Präfix I- weg. Machen Sie sich jedes Mal eine mentale Notiz, wenn Sie Code wie class Something: ISomething Sehen - 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 Originalpapiers OOP) 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 für die Substitution vorgesehen ist (dh nicht endgültig/versiegelt), funktionieren muss jede konforme Implementierung dieses Typs.

8
Luaan

Wie sollen diese beiden beispielsweise benannt werden? Parser und ConcreteParser? Parser und ParserImplementation?

In einer idealen Welt sollten Sie Ihrem Namen keine Kurzschrift ("I ...") voranstellen, sondern den Namen einfach ein Konzept ausdrücken lassen.

Ich habe irgendwo die Meinung gelesen (und kann sie nicht beziehen), dass diese ISomething-Konvention dazu führt, dass Sie ein "IDollar" -Konzept 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 Aus und das einfache Aus ist subtil falsch .


In der realen Welt müssen die Clients Ihres Codes (die möglicherweise nur "Sie aus 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 den Clients) von Ihrem Code, was sie erwarten zu bekommen).

Die ISomething-Konvention führt Cruft/Bad Names ein. Die Cruft ist jedoch keine echte Cruft *), es sei denn, die beim Schreiben des Codes verwendeten Konventionen und Praktiken sind nicht mehr aktuell. Wenn die Konvention "use ISomething" sagt, ist es am besten, es zu verwenden, um sich an andere Entwickler anzupassen (und besser zu arbeiten).


*) Ich kann damit durchkommen, dass "Cruft" sowieso kein genaues Konzept ist :)

3
utnapistim

Wie in den anderen Antworten erwähnt, ist das Präfixieren Ihrer Schnittstellennamen mit I Teil der Codierungsrichtlinien für das .NET Framework. Da mit konkreten Klassen in C # und VB.NET häufiger interagiert wird als mit generischen Schnittstellen, sind sie in Namensschemata erstklassig und sollten daher die einfachsten Namen haben - daher IParser für die Schnittstelle und Parser für die Standardimplementierung.

Aber im Fall von Java und ähnlichen Sprachen, in denen Schnittstellen anstelle konkreter Klassen bevorzugt werden, hat Onkel Bob Recht, dass I entfernt werden sollte und Schnittstellen die einfachsten haben sollten Namen - Parser zum Beispiel für Ihre Parser-Schnittstelle. Anstelle eines rollenbasierten Namens wie ParserDefault sollte die Klasse jedoch einen Namen haben, der wie den Parser beschreibt ist implementiert:

public interface Parser{
}

public class RegexParser implements Parser {
    // parses using regular expressions
}

public class RecursiveParser implements Parser {
    // parses using a recursive call structure
}

public class MockParser implements Parser {
    // BSes the parsing methods so you can get your tests working
}

So funktioniert zum Beispiel Set<T>, HashSet<T>, und TreeSet<T> sind benannt.

2
TheHansinator