Präfixe und Suffixe gehören nicht in Interface-Namen
Die Verwendung des Präfixes I
oder des Suffixes
Interface
bei Interfaces, ebenso wie Abstract
bei
abstrakten Klassen, ist ein Antipattern. In sauberem Code hat es nichts zu
suchen. Die Unterscheidung von Interface-Namen verschleiert tatsächlich die
Prinzipien der OOP, bringt Rauschen in den Code und verursacht Komplikationen
bei der Entwicklung. Die Gründe sind folgende.

Typ = Klasse + Interface + Nachkommen
In der Welt der OOP werden sowohl Klassen als auch Interfaces als Typen betrachtet. Wenn ich einen Typ bei der Deklaration einer Property oder eines Parameters verwende, gibt es aus Entwicklersicht keinen Unterschied, ob der Typ, auf den man sich verlässt, eine Klasse oder ein Interface ist. Das ist eine großartige Sache, dank der Interfaces eigentlich so nützlich sind. Das gibt ihrer Existenz einen Sinn. (Ernsthaft: Wozu wären Interfaces gut, wenn dieses Prinzip nicht gelten würde? Versuchen Sie, darüber nachzudenken.)
Schauen Sie sich diesen Code an:
class FormRenderer
{
public function __construct(
private Form $form,
private Translator $translator,
) {
}
}
Der Konstruktor sagt: “Ich brauche ein Formular und einen
Übersetzer.” Und es ist ihm völlig egal, ob er ein
GettextTranslator
- oder DatabaseTranslator
-Objekt
bekommt. Und gleichzeitig ist es mir als Benutzer völlig egal, ob
Translator
ein Interface, eine abstrakte Klasse oder eine konkrete
Klasse ist.
Ist es mir völlig egal? Eigentlich nicht, ich gebe zu, ich bin ziemlich neugierig, also wenn ich eine fremde Bibliothek untersuche, schaue ich nach, was sich hinter dem Typ verbirgt, und fahre mit der Maus darüber:

Aha, jetzt weiß ich es. Und damit endet es. Das Wissen, ob es sich um eine Klasse oder ein Interface handelt, wäre wichtig, wenn ich eine Instanz davon erstellen wollte, aber das ist hier nicht der Fall, ich spreche nur über den Typ der Variablen. Und hier möchte ich von diesen Details abgeschirmt sein. Und ich will sie schon gar nicht in meinen Code einschleppen! Was sich hinter dem Typ verbirgt, ist Teil seiner Definition, nicht des Typs selbst.
Und jetzt schauen Sie sich den nächsten Code an:
class FormRenderer
{
public function __construct(
private AbstractForm $form,
private TranslatorInterface $translator,
) {
}
}
Diese Konstruktordefinition sagt wörtlich: “Ich brauche ein abstraktes Formular und ein Interface für den Übersetzer.” Aber das ist Unsinn. Er braucht ein konkretes Formular, das er rendern soll. Kein abstraktes Formular. Und er braucht ein Objekt, das die Aufgabe des Übersetzers erfüllt. Er braucht kein Interface.
Sie wissen, dass die Wörter Interface
und Abstract
ignoriert werden sollen. Dass der Konstruktor dasselbe will wie im vorherigen
Beispiel. Aber… ernsthaft? Finden Sie es wirklich eine gute Idee, in
Namenskonventionen die Verwendung von Wörtern einzuführen, die übersehen
werden sollen?
Denn es erzeugt eine falsche Vorstellung von den Prinzipien der OOP. Ein
Anfänger muss verwirrt sein: „Wenn unter dem Typ Translator
entweder 1) ein Objekt der Klasse Translator
2) ein Objekt, das das
Interface Translator
implementiert oder 3) ein davon erbendes
Objekt verstanden wird, was wird dann unter TranslatorInterface
verstanden?“ Darauf gibt es keine vernünftige Antwort.
Wenn wir TranslatorInterface
schreiben, obwohl auch
Translator
ein Interface sein kann, begehen wir eine Tautologie.
Dasselbe, wenn wir interface TranslatorInterface
deklarieren. Und
so weiter. Bis ein Programmiererwitz entsteht:
interface TranslatorInterface
{
}
class FormRendererClass
{
/**
* Konstruktor
*/
public function __construct(
private AbstractForm $privatePropertyForm,
private TranslatorInterface $privatePropertyTranslator,
) {
// 🤷♂️
}
}
Die außergewöhnliche Implementierung
Wenn ich so etwas wie TranslatorInterface
sehe, ist es
wahrscheinlich, dass es auch eine Implementierung namens
Translator implements TranslatorInterface
geben wird. Das bringt
mich zum Nachdenken: Was macht Translator
so besonders, dass er das
alleinige Recht hat, sich Translator zu nennen? Jede andere Implementierung
benötigt einen beschreibenden Namen, zum Beispiel
GettextTranslator
oder DatabaseTranslator
, aber diese
ist irgendwie “Standard”, wie ihre bevorzugte Stellung andeutet, wenn sie
Translator
ohne Zusatz heißt.
Das verunsichert die Leute sogar und sie wissen nicht, ob sie den Typehint
für Translator
oder TranslatorInterface
schreiben
sollen. Im Client-Code wird dann beides gemischt, sicher sind Sie schon oft
darauf gestoßen (in Nette zum Beispiel im Zusammenhang mit
Nette\Http\Request
vs IRequest
).
Wäre es nicht besser, die außergewöhnliche Implementierung loszuwerden und
den allgemeinen Namen Translator
für das Interface beizubehalten?
Also konkrete Implementierungen mit konkretem Namen + allgemeines Interface mit
allgemeinem Namen. Das ergibt doch Sinn.
Die Last des beschreibenden Namens liegt dann rein bei den Implementierungen.
Wenn wir TranslatorInterface
in Translator
umbenennen,
braucht unsere ehemalige Klasse Translator
einen neuen Namen. Leute
neigen dazu, dieses Problem zu lösen, indem sie sie
DefaultTranslator
nennen, auch ich bin schuldig. Aber wieder, was
macht sie so besonders, dass sie Default heißt? Seien Sie nicht faul und denken
Sie gründlich darüber nach, was sie tut und warum sie sich von anderen
möglichen Implementierungen unterscheidet.
Und was, wenn ich mir nicht mehrere Implementierungen vorstellen kann? Was, wenn mir nur eine gültige Methode einfällt? Dann erstellen Sie einfach kein Interface. Zumindest vorerst.
Siehe da, eine weitere Implementierung ist aufgetaucht
Und da ist es! Wir brauchen eine zweite Implementierung. Das passiert häufig. Es gab nie die Notwendigkeit, Übersetzungen anders als auf eine bewährte Weise zu speichern, z.B. in einer Datenbank, aber jetzt gibt es eine neue Anforderung und es müssen mehrere Übersetzer in der Anwendung vorhanden sein.
Das ist auch der Moment, in dem Sie klar erkennen, was die Spezifität des ursprünglichen einzigen Übersetzers war. Es war ein Datenbank-Übersetzer, kein Standard.
Was tun?
- Aus dem Namen
Translator
machen wir ein Interface - Die ursprüngliche Klasse benennen Sie in
DatabaseTranslator
um und sie implementiertTranslator
- Und Sie erstellen neue Klassen
GettextTranslator
und vielleichtNeonTranslator
All diese Änderungen lassen sich sehr bequem und einfach durchführen,
insbesondere wenn die Anwendung gemäß den Prinzipien der Dependency
Injection aufgebaut ist. Im Code muss nichts geändert werden, nur in
der Konfiguration des DI-Containers ändern wir Translator
in
DatabaseTranslator
. Das ist großartig!
Eine diametral andere Situation würde jedoch eintreten, wenn wir auf
Präfixen/Suffixen bestehen würden. Wir müssten die Typen in der gesamten
Anwendung von Translator
in TranslatorInterface
umbenennen. Eine solche Umbenennung wäre rein zweckmäßig, um die Konvention
einzuhalten, würde aber dem Sinn der OOP widersprechen, wie wir gerade gezeigt
haben. Das Interface hat sich nicht geändert, der Benutzercode hat sich nicht
geändert, aber die Konvention erfordert eine Umbenennung? Dann ist es eine
fehlerhafte Konvention.
Wenn sich außerdem im Laufe der Zeit herausstellen würde, dass eine abstrakte Klasse besser wäre als ein Interface, würden wir erneut umbenennen. Ein solcher Eingriff muss keineswegs trivial sein, etwa wenn der Code auf mehrere Pakete verteilt ist oder von Dritten verwendet wird.
Aber das machen doch alle so
Nicht alle. Es stimmt, dass in der PHP-Welt das Zend Framework und danach Symfony, also große Player, die Unterscheidung von Interface- und abstrakten Klassennamen popularisiert haben. Dieser Ansatz wurde auch von PSR übernommen, die paradoxerweise nur Interfaces veröffentlicht und dennoch bei jedem das Wort Interface im Namen angibt.
Andererseits unterscheidet ein anderes bedeutendes Framework, Laravel,
Interfaces und abstrakte Klassen in keiner Weise. Das tut zum Beispiel auch die
populäre Datenbankschicht Doctrine nicht. Und das tut auch die
Standardbibliothek in PHP nicht (wir haben Interfaces Throwable
oder Iterator
, die abstrakte Klasse
FilterIterator
, usw.).
Wenn wir einen Blick über die PHP-Welt hinaus werfen, verwendet C# das
Präfix I
für Interfaces, während in Java oder TypeScript die
Namen nicht unterschieden werden.
Es machen also nicht alle, aber selbst wenn sie es täten, bedeutet das nicht, dass es gut ist. Gedankenlos zu übernehmen, was andere tun, ist nicht vernünftig, denn man kann auch Fehler übernehmen. Fehler, die der andere vielleicht selbst gerne loswerden würde, aber es ist zu aufwendig.
Ich erkenne im Code nicht, was ein Interface ist
Viele Programmierer werden einwenden, dass Präfixe/Suffixe für sie nützlich sind, da sie dank ihnen sofort im Code erkennen, was Interfaces sind. Sie haben das Gefühl, dass ihnen eine solche Unterscheidung fehlen würde. Mal sehen, erkennen Sie in diesen Beispielen, was eine Klasse und was ein Interface ist?
$o = new X;
class X extends X implements Y
{}
interface Y
{}
X::fn();
X::$v;
X
ist immer eine Klasse, Y
ist ein Interface, es
ist eindeutig auch ohne Präfixe/Postfixe. Natürlich weiß das auch die IDE und
wird Ihnen im jeweiligen Kontext immer korrekt Vorschläge machen.
Aber was ist hier:
function foo(A $param): A
{}
public A $property;
$o instanceof A
A::CONST
try {
} catch (A $x) {
}
In diesen Fällen erkennen Sie es nicht. Wie wir ganz am Anfang gesagt haben, soll hier aus Entwicklersicht kein Unterschied bestehen, was eine Klasse und was ein Interface ist. Was eben Interfaces und abstrakten Klassen ihren Sinn gibt.
Wenn Sie hier in der Lage wären, eine Klasse von einem Interface zu unterscheiden, würde dies das grundlegende Prinzip der OOP negieren. Und Interfaces würden ihren Sinn verlieren.
Ich bin daran gewöhnt
Gewohnheiten zu ändern tut einfach weh 🙂 Manchmal schon die Vorstellung davon. Aber um fair zu sein, viele Menschen werden von Veränderungen angezogen und freuen sich darauf, aber für die Mehrheit gilt, dass Gewohnheit eine eiserne Fessel ist.
Aber schauen Sie einfach in die Vergangenheit, wie einige Gewohnheiten von
der Zeit verweht wurden. Die wohl berühmteste ist die sogenannte Ungarische
Notation, die seit den achtziger Jahren verwendet und von Microsoft
popularisiert wurde. Die Notation bestand darin, dass der Name jeder Variablen
mit einer Abkürzung begann, die ihren Datentyp symbolisierte. In PHP sähe das
so aus: echo $this->strName
oder
$this->intCount++
. Von der Ungarischen Notation begann man sich
in den neunziger Jahren zu lösen, und heute rät Microsoft in seinen
Richtlinien Entwicklern direkt davon ab.
Einst war sie unverzichtbar, und heute vermisst sie niemand.
Aber warum so weit in die Vergangenheit gehen? Vielleicht erinnern Sie sich, dass es in PHP üblich war, nicht-öffentliche Klassenmitglieder mit einem Unterstrich zu unterscheiden (Beispiel aus dem Zend Framework). Das war zu einer Zeit, als es längst PHP 5 gab, das die Sichtbarkeitsmodifikatoren public/protected/private hatte. Aber Programmierer taten es aus Gewohnheit. Sie waren überzeugt, dass sie sich ohne Unterstriche im Code nicht mehr zurechtfinden würden. „Wie würde ich im Code öffentliche von privaten Variablen unterscheiden, aha?“
Heute verwendet niemand mehr Unterstriche. Und niemand vermisst sie. Die Zeit hat hervorragend bewiesen, dass die Befürchtungen unbegründet waren.
Dabei ist es genau dasselbe wie der Einwand: „Wie würde ich im Code Interfaces von Klassen unterscheiden, aha?“
Ich habe vor zehn Jahren aufgehört, Präfixe/Postfixe zu verwenden. Ich würde niemals zurückkehren, es war eine großartige Entscheidung. Ich kenne auch keinen anderen Programmierer, der zurückkehren möchte. Wie ein Freund sagte: „Probier es aus, und in einem Monat wirst du nicht verstehen, dass du es jemals anders gemacht hast.“
Ich möchte Konsistenz wahren
Ich kann mir vorstellen, dass ein Programmierer sagt: „Präfixe und Suffixe zu verwenden ist wirklich Unsinn, ich verstehe das, aber ich habe meinen Code bereits so aufgebaut und eine Änderung ist sehr schwierig. Und wenn ich anfangen würde, neuen Code korrekt ohne sie zu schreiben, entsteht eine Inkonsistenz, die vielleicht noch schlimmer ist als eine schlechte Konvention.“
Tatsächlich ist Ihr Code bereits jetzt inkonsistent, da Sie die PHP-Systembibliothek verwenden, die keine Präfixe und Postfixe hat:
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
public function add(string|Stringable $item): void
{
}
}
Und Hand aufs Herz, stört das? Ist Ihnen jemals eingefallen, dass dies konsistenter wäre?
class Collection implements ArrayAccessInterface, CountableInterface, IteratorAggregateInterface
{
public function add(string|StringableInterface $item): void
{
}
}
Oder dies?
try {
$command = $this->find($name);
} catch (ThrowableInterface $e) {
return $e->getMessage();
}
Ich denke nicht. Konsistenz spielt keine so bedeutende Rolle, wie es scheinen mag. Im Gegenteil, das Auge bevorzugt weniger visuelles Rauschen, das Gehirn die Sauberkeit des Entwurfs. Daher ergibt es Sinn, die Konvention anzupassen und neue Interfaces korrekt ohne Präfixe und Suffixe zu schreiben.
Sie können auch gezielt aus großen Projekten entfernt werden. Ein Beispiel
ist das Nette Framework, das historisch Präfixe I
in
Interface-Namen verwendete, wovon es sich vor einigen Jahren schrittweise
und unter vollständiger Wahrung der Abwärtskompatibilität entfernt.
Um einen Kommentar abzugeben, loggen Sie sich bitte ein