Verwirrt über die Schnittstellen- und Klassencodierungsrichtlinien für TypeScript

79

Ich habe die TypeScript-Codierungsrichtlinien gelesen

Und ich fand diese Aussage ziemlich rätselhaft:

Verwenden Sie "I" nicht als Präfix für Schnittstellennamen

Ich meine, so etwas würde ohne das Präfix "I" nicht viel Sinn machen

class Engine implements IEngine

Vermisse ich etwas Offensichtliches?

Eine andere Sache, die ich nicht ganz verstand, war folgende:

Klassen

Verwenden Sie aus Gründen der Konsistenz keine Klassen in der Core-Compiler-Pipeline. Verwenden Sie stattdessen Funktionsabschlüsse.

Bedeutet das, dass ich überhaupt keine Klassen verwenden sollte?

Hoffe jemand kann es für mich klären :)

Snæbjørn
quelle
12
Zur Verdeutlichung ist dies die Dokumentation zum Stil des Codes für TypeScript und keine Stilrichtlinie für die Implementierung Ihres Projekts. Wenn die Verwendung des IPräfixes für Sie und Ihr Team sinnvoll ist, verwenden Sie es. Wenn nicht, vielleicht den Java-Stil von SomeThing(Schnittstelle) mit SomeThingImpl(Implementierung), dann verwenden Sie das auf jeden Fall.
Brocco
Oh, das erklärt einige Dinge :)
Snæbjørn
Sicherlich :), ich habe meinen Kommentar als Antwort nach unten verschoben
Brocco
2
Das Schöne an Codierungsrichtlinien ist, dass es sich um Richtlinien handelt , sodass Sie wählen können, ob Sie sie implementieren möchten oder nicht. Persönlich bezeichne ich Ieine Schnittstelle, weil es das ist, was ich gewohnt bin und es für die Leute in meinem Team sinnvoll ist.
Jon Preece
3
Als mein Team anfing, TS zu verwenden, verwendeten wir auch das I-Präfix für Schnittstellen. C # Einfluss, nehme ich an. Dann habe ich die Richtlinie des TypeScript-Teams gelesen, in der es darum geht, kein I-Präfix zu verwenden. Ich habe mehrere Wochen gebraucht, um es zu realisieren. Ich suchte eifrig nach Informationen, warum sie solche Regeln haben. Nach einiger Zeit wurde mir klar: Das I-Präfix für Schnittstellen ist ein Fehler (zumindest in TypeScript), es verursacht mehr Probleme als Vorteile.
Stanislav Berkov

Antworten:

159

Wenn ein Team / Unternehmen ein Framework / Compiler / Tool-Set ausliefert, verfügt es bereits über einige Erfahrungen und Best Practices. Sie teilen es als Richtlinien. Richtlinien sind Empfehlungen . Wenn Sie keine mögen, können Sie sie ignorieren. Der Compiler kompiliert weiterhin Ihren Code. Aber wenn in Rom ...

Dies ist meine Vision, warum das TypeScript-Team empfiehlt, ISchnittstellen nicht zu fixieren.

Grund Nr. 1 Die Zeiten der ungarischen Notation sind vorbei

Das Hauptargument von IUnterstützern von -prefix-for-interface ist, dass das Präfix hilfreich ist, um sofort zu prüfen, ob der Typ eine Schnittstelle ist. Die Aussage, dass das Präfix hilfreich ist, um sofort zu grokken (spähen), ist ein Appell an die ungarische Notation . IPräfix für Schnittstellenname, CKlasse, Aabstrakte Klasse, sZeichenfolgenvariable, cKonstantenvariable, iGanzzahlvariable. Ich bin damit einverstanden, dass eine solche Namensdekoration Ihnen Typinformationen liefern kann, ohne mit der Maus über die Kennung zu fahren oder über einen Hotkey zur Typdefinition zu navigieren. Dieser winzige Vorteil wird durch die nachstehend genannten ungarischen Notationsnachteile und andere Gründe aufgewogen. Die ungarische Notation wird in zeitgenössischen Rahmenbedingungen nicht verwendet. C # hatIPräfix (und dies ist das einzige Präfix in C #) für Schnittstellen aus historischen Gründen (COM). Rückblickend ist einer der .NET-Architekten (Brad Abrams) der Meinung, dass es besser gewesen wäre, kein IPräfix zu verwenden . TypeScript ist COM-Legacy-frei, daher gibt es keine IPräfix-für-Schnittstelle-Regel.

Grund # 2- IPräfix verstößt gegen das Kapselungsprinzip

Nehmen wir an, Sie bekommen eine Blackbox. Sie erhalten eine Typreferenz, mit der Sie mit diesem Feld interagieren können. Es sollte Ihnen egal sein, ob es sich um eine Schnittstelle oder eine Klasse handelt. Sie verwenden nur den Schnittstellenteil. Die Forderung zu wissen, was es ist (Schnittstelle, spezifische Implementierung oder abstrakte Klasse), ist eine Verletzung der Kapselung.

Beispiel: Nehmen wir an, Sie müssen API Design Mythos: Schnittstelle als Vertrag in Ihrem Code korrigieren, z. B. ICarSchnittstelle löschen und Carstattdessen Basisklasse verwenden. Dann müssen Sie einen solchen Austausch bei allen Verbrauchern durchführen. I-prefix führt zu einer impliziten Abhängigkeit der Verbraucher von Black-Box-Implementierungsdetails.

Grund Nr. 3 Schutz vor schlechten Namen

Entwickler sind faul, über Namen richtig nachzudenken. Das Benennen ist eines der beiden schwierigen Dinge in der Informatik . Wenn ein Entwickler eine Schnittstelle extrahieren muss, ist es einfach, den Buchstaben Izum Klassennamen hinzuzufügen, und Sie erhalten einen Schnittstellennamen. Wenn Sie das IPräfix für Schnittstellen nicht zulassen , müssen Entwickler ihr Gehirn belasten, um geeignete Namen für Schnittstellen auszuwählen. Ausgewählte Namen sollten sich nicht nur im Präfix unterscheiden, sondern auch den Unterschied zwischen den Absichten betonen.

Abstraction Fall: Sie sollten nicht keine definieren ICarSchnittstelle und eine zugehörige CarKlasse. Carist eine Abstraktion und sollte für den Vertrag verwendet werden. Implementierungen sollten beschreibende, eindeutige Namen haben, z SportsCar, SuvCar, HollowCar.

Gutes Beispiel: WpfeServerAutosuggestManager implements AutosuggestManager, FileBasedAutosuggestManager implements AutosuggestManager.

Schlechtes Beispiel : AutosuggestManager implements IAutosuggestManager.

Grund Nr. 4 Richtig ausgewählte Namen impfen Sie gegen API Design Mythos: Schnittstelle als Vertrag .

In meiner Praxis habe ich viele Leute getroffen, die gedankenlos einen Schnittstellenteil einer Klasse in einer separaten Schnittstelle mit Car implements ICarNamensschema dupliziert haben . Das Duplizieren eines Schnittstellenteils einer Klasse in einem separaten Schnittstellentyp konvertiert sie nicht auf magische Weise in eine Abstraktion. Sie erhalten weiterhin eine konkrete Implementierung, jedoch mit einem doppelten Schnittstellenteil. Wenn Ihre Abstraktion nicht so gut ist, wird sie durch das Duplizieren des Schnittstellenteils ohnehin nicht verbessert. Abstraktion zu extrahieren ist harte Arbeit.

HINWEIS: In TS benötigen Sie keine separate Schnittstelle zum Verspotten von Klassen oder zum Überladen von Funktionen. Anstatt eine separate Schnittstelle zu erstellen, die öffentliche Mitglieder einer Klasse beschreibt, können Sie TypeScript-Dienstprogrammtypen verwenden . Required<T>Konstruiert zB einen Typ, der aus allen öffentlichen Mitgliedern des Typs besteht T.

export class SecurityPrincipalStub implements Required<SecurityPrincipal> {
  public isFeatureEnabled(entitlement: Entitlement): boolean {
      return true;
  }
  public isWidgetEnabled(kind: string): boolean {
      return true;
  }

  public areAdminToolsEnabled(): boolean {
      return true;
  }
}

Wenn Sie einen Typ ohne einige öffentliche Mitglieder erstellen möchten, können Sie die Kombination aus Auslassen und Ausschließen verwenden .

Stanislav Berkov
quelle
3
Ich mag Ihren Standpunkt, konkrete Klassen spezifischer als Schnittstellennamen zu benennen, aber ich denke, das IPräfix ist hilfreich, um sofort zu prüfen, ob der Typ eine Implementierung hat oder nicht. Der Hauptvorteil bei der Verwendung von Schnittstellen besteht darin, dass die Implementierungsdetails von externem Code nicht an eine bestimmte Implementierung gekoppelt werden.
Cchamberlain
1
@demisx Wenn es sich unangenehm anfühlt, sollten Sie es nicht tun. Beantworten Sie die Frage: Brauchen Sie sie wirklich? Machen sie irgendeinen Sinn oder verwenden Sie sie nur für das falsche Gefühl, die richtige Technik zu machen?
Stanislav Berkov
2
Während Ihre Punkte theoretisch gut klingen, ist die Realität, dass das Präfix "I" häufig in ausreichend großen Teams gegen das Suffix "Impl"
eingetauscht wird
3
@jameslk Ich weiß, Dinge ohne zu benennen Iund Implist schwierig ("Es gibt nur zwei schwierige Dinge in der Informatik: Cache-Ungültigmachung und Benennung von Dingen" - Phil Karlton), aber es ist machbar.
Stanislav Berkov
5
Ich bin damit einverstanden, dass das Entfernen von I normalerweise durch Hinzufügen eines Präfixes oder Suffix zu den Implementierungen ersetzt wird: DefaultFooService oder FoodServiceImpl. Mit dem Präfix erhalten Sie Grokking und keine überlappenden Namensprobleme zwischen Verträgen und Implementierungen / Abstraktionen. Auch der Punkt zum Wiederholen ist gültig, aber der Punkt der Schnittstellen besteht darin, Verträge zu haben und die Implementierung durch etwas anderes ersetzen zu können. Normalerweise ist dies beim Testen von Einheiten absolut entscheidend ... ganz zu schweigen von den Vorteilen der Abstraktionen und der Unveränderlichkeit usw. Ich bin alles für die Schnittstellen, die ich vorstelle.
Chris
45

Klarstellung bezüglich des Links, auf den Sie verweisen:

Dies ist die Dokumentation zum Stil des Codes für TypeScript und keine Stilrichtlinie für die Implementierung Ihres Projekts.

Wenn die Verwendung des IPräfixes für Sie und Ihr Team sinnvoll ist, verwenden Sie es (das tue ich).

Wenn nicht, vielleicht den Java-Stil von SomeThing(Schnittstelle) mit SomeThingImpl(Implementierung), dann verwenden Sie das auf jeden Fall.

Brocco
quelle
18
Nur zur Klarstellung. Das Typoskript-Handbuch sagt immer noch: In general, do not prefix interfaces with I (e.g. IColor). Because the concept of an interface in TypeScript is much more broad than in C# or Java, the IFoo naming convention is not broadly useful.Es ist nicht streng, sieht aber nach einer starken Empfehlung aus.
Guria
3
Zustimmen. Es klingt wie eine Code-Richtlinie von Microsoft für alle TypeScript-basierten Projekte, nicht nur für TypeScript selbst. Wir fanden diese Richtlinie sehr verwirrend und stellen allen Schnittstellen immer noch "I" voran.
Demisx
3
Es gibt ein Szenario, das ich für sinnvoll halte I. In einem React-Projekt Cardheißt das eine Schnittstelle und eine Komponente Card. In den Requisiten brauche ich ein Array von Card, aber der Typ nicht die Komponente. Ich muss wählen, um zu benennen ICardoder CardComponent...
BrunoLM
3
SomeThingImpl implements SomeThingist so gut wie SomeThing implements ISomeThing. Wenn SomeThinges sich um eine Abstraktion handelt, sollte die Benennung erfolgen Concrete[Some]Thing implements SomeThing.
Stanislav Berkov
Wie von @Guria gesagt, sagt das TypeScript-Handbuch alles, wir müssen nur auf die Beispiele achten und wir werden wissen, wie man es gut benutzt.
Giovannipds
1

Der Typ, der eine Schnittstelle ist, ist ein Implementierungsdetail. Implementierungsdetails sollten in der API ausgeblendet werden: s. Deshalb sollten Sie vermeiden I.

Sie sollten beide prefix und vermeiden suffix. Beides ist falsch:

  • ICar
  • CarInterface

Was Sie tun sollten, ist, einen hübschen Namen in der API sichtbar zu machen und das Implementierungsdetail in der Implementierung auszublenden. Deshalb schlage ich vor:

  • Car - Eine Schnittstelle, die in der API verfügbar gemacht wird.
  • CarImpl - Eine Implementierung dieser API, die dem Verbraucher verborgen bleibt.
Tomas Bjerre
quelle
Derzeit verwende ich auch diese Struktur, die aus meinem SpringBoot-Hintergrund erstellt wurde. Also wird dies in TS als richtig angesehen? In der TS-Community scheint es viele Meinungen darüber zu geben, was richtig ist, ohne endgültige Antwort.
Unreinheit
2
Die Verwendung des ImplSuffixes hat den Nebeneffekt, dass Sie den Besserwisser ersetzen, der Sie für das IPräfix mit Onkel Bob beschimpft .
Szczepan Hołyszewski