Les préfixes et suffixes n'ont pas leur place dans les noms d'interface
L'utilisation du préfixe I
ou du suffixe Interface
pour les interfaces, ainsi que Abstract
pour les classes
abstraites, est un antipattern. Il n'a rien à faire dans du code propre. La
distinction des noms d'interface obscurcit en réalité les principes de la POO,
introduit du bruit dans le code et complique le développement. Les raisons sont
les suivantes.

Type = classe + interface + descendants
Dans le monde de la POO, les classes et les interfaces sont considérées comme des types. Si j'utilise un type lors de la déclaration d'une propriété ou d'un paramètre, il n'y a aucune différence du point de vue du développeur entre le fait que le type sur lequel il s'appuie soit une classe ou une interface. C'est une chose formidable, grâce à laquelle les interfaces sont en fait si utiles. C'est ce qui donne un sens à leur existence. (Sérieusement : à quoi serviraient les interfaces si ce principe ne s'appliquait pas ? Essayez d'y réfléchir.)
Regardez ce code :
class FormRenderer
{
public function __construct(
private Form $form,
private Translator $translator,
) {
}
}
Le constructeur dit : “J'ai besoin d'un formulaire et d'un
traducteur.” Et il se fiche complètement de savoir s'il reçoit un objet
GettextTranslator
ou DatabaseTranslator
. Et en même
temps, en tant qu'utilisateur, je me fiche complètement de savoir si
Translator
est une interface, une classe abstraite ou une classe
concrète.
Je m'en fiche complètement ? En fait non, j'avoue que je suis assez curieux, alors quand j'examine une bibliothèque étrangère, je jette un coup d'œil à ce qui se cache derrière le type, et je passe la souris dessus :

Ah, maintenant je sais. Et ça s'arrête là. Savoir s'il s'agit d'une classe ou d'une interface serait important si je voulais créer son instance, mais ce n'est pas le cas, je parle juste du type de la variable. Et ici, je veux être isolé de ces détails. Et je ne veux surtout pas les introduire dans mon code ! Ce qui se cache derrière le type fait partie de sa définition, pas du type lui-même.
Et maintenant, regardez cet autre code :
class FormRenderer
{
public function __construct(
private AbstractForm $form,
private TranslatorInterface $translator,
) {
}
}
Cette définition de constructeur dit littéralement : “J'ai besoin d'un formulaire abstrait et d'une interface de traducteur.” Mais c'est une bêtise. Il a besoin d'un formulaire concret à afficher. Pas d'un formulaire abstrait. Et il a besoin d'un objet qui remplit le rôle de traducteur. Il n'a pas besoin d'une interface.
Vous savez que les mots Interface
et Abstract
doivent être ignorés. Que le constructeur veut la même chose que dans
l'exemple précédent. Mais… sérieusement ? Pensez-vous vraiment que c'est
une bonne idée d'introduire dans les conventions de nommage l'utilisation de
mots qui doivent être ignorés ?
Cela crée une fausse idée des principes de la POO. Un débutant doit être
confus : « Si le type Translator
signifie soit 1) un objet de la
classe Translator
2) un objet implémentant l'interface
Translator
ou 3) un objet en héritant, que signifie alors
TranslatorInterface
? » Il n'y a pas de réponse raisonnable
à cela.
Lorsque nous écrivons TranslatorInterface
, bien que
Translator
puisse également être une interface, nous commettons
une tautologie. Idem lorsque nous déclarons
interface TranslatorInterface
. Et ainsi de suite. Jusqu'à ce
qu'une blague de programmeur apparaisse :
interface TranslatorInterface
{
}
class FormRendererClass
{
/**
* Constructeur
*/
public function __construct(
private AbstractForm $privatePropertyForm,
private TranslatorInterface $privatePropertyTranslator,
) {
// 🤷♂️
}
}
Implémentation exceptionnelle
Quand je vois quelque chose comme TranslatorInterface
, il est
probable qu'il existera aussi une implémentation nommée
Translator implements TranslatorInterface
. Cela me fait réfléchir
: qu'est-ce qui rend Translator
si exceptionnel qu'il a le droit
unique de s'appeler Translator ? Toute autre implémentation a besoin d'un nom
descriptif, par exemple GettextTranslator
ou
DatabaseTranslator
, mais celle-ci est en quelque sorte “par
défaut”, comme l'indique sa position privilégiée, puisqu'elle s'appelle
Translator
sans qualificatif.
Cela rend même les gens incertains et ils ne savent pas s'ils doivent
écrire un typehint pour Translator
ou
TranslatorInterface
. Dans le code client, les deux se mélangent
alors, vous l'avez certainement déjà rencontré de nombreuses fois (dans Nette
par exemple en relation avec Nette\Http\Request
vs
IRequest
).
Ne serait-il pas préférable de se débarrasser de l'implémentation
exceptionnelle et de conserver le nom générique Translator
pour
l'interface ? C'est-à-dire avoir des implémentations concrètes avec un nom
concret + une interface générique avec un nom générique. Cela a du sens,
n'est-ce pas ?
Le fardeau du nom descriptif repose alors uniquement sur les
implémentations. Si nous renommons TranslatorInterface
en
Translator
, notre ancienne classe Translator
a besoin
d'un nouveau nom. Les gens ont tendance à résoudre ce problème en l'appelant
DefaultTranslator
, je suis moi-même coupable. Mais encore une
fois, qu'est-ce qui la rend si exceptionnelle qu'elle s'appelle Default ? Ne
soyez pas paresseux et réfléchissez bien à ce qu'elle fait et pourquoi cela
diffère des autres implémentations possibles.
Et si je ne peux pas imaginer plusieurs implémentations ? Et si une seule méthode valide me vient à l'esprit ? Alors ne créez tout simplement pas d'interface. Du moins pour l'instant.
Tiens, une autre implémentation est apparue
Et voilà ! Nous avons besoin d'une deuxième implémentation. Cela arrive couramment. Il n'y a jamais eu besoin de stocker les traductions autrement que d'une seule manière éprouvée, par exemple dans une base de données, mais maintenant une nouvelle exigence est apparue et il faut avoir plusieurs traducteurs dans l'application.
C'est aussi le moment où vous réalisez clairement quelle était la spécificité du traducteur unique d'origine. C'était un traducteur de base de données, pas un traducteur par défaut.
Que faire ?
- Faites du nom
Translator
une interface - Renommez la classe d'origine en
DatabaseTranslator
et faites-la implémenterTranslator
- Et créez de nouvelles classes
GettextTranslator
et peut-êtreNeonTranslator
Tous ces changements se font très confortablement et facilement, surtout si
l'application est construite conformément aux principes de l'injection de
dépendances. Il n'est pas nécessaire de modifier quoi que ce soit dans
le code, seulement dans la configuration du conteneur DI, nous changeons
Translator
en DatabaseTranslator
. C'est génial !
Une situation diamétralement différente se produirait cependant si nous
insistions sur le préfixage/suffixage. Nous devrions renommer les types de
Translator
en TranslatorInterface
dans toute
l'application. Un tel renommage serait purement fonctionnel pour respecter la
convention, mais irait à l'encontre du sens de la POO, comme nous l'avons
montré tout à l'heure. L'interface n'a pas changé, le code utilisateur n'a
pas changé, mais la convention exige de renommer ? Alors c'est une mauvaise
convention.
De plus, si avec le temps il s'avérait qu'une classe abstraite serait meilleure qu'une interface, nous renommerions à nouveau. Une telle intervention peut ne pas être triviale du tout, par exemple si le code est réparti sur plusieurs paquets ou s'il est utilisé par des tiers.
Mais tout le monde le fait comme ça
Pas tout le monde. Il est vrai que dans le monde PHP, la distinction des noms des interfaces et des classes abstraites a été popularisée par Zend Framework puis par Symfony, c'est-à-dire de grands acteurs. Cette approche a également été adoptée par PSR, qui paradoxalement ne publie que des interfaces, et pourtant indique pour chacune le mot interface dans le nom.
D'un autre côté, un autre framework important, Laravel, ne distingue en
aucune façon les interfaces et les classes abstraites. La populaire couche de
base de données Doctrine ne le fait pas non plus, par exemple. Et la
bibliothèque standard de PHP ne le fait pas non plus (nous avons ainsi les
interfaces Throwable
ou Iterator
, la classe abstraite
FilterIterator
, etc.).
Si nous regardions en dehors du monde PHP, par exemple C# utilise le préfixe
I
pour les interfaces, tandis qu'en Java ou TypeScript, les noms ne
sont pas distingués.
Donc, tout le monde ne le fait pas, mais même s'ils le faisaient, cela ne signifie pas que c'est bien. Adopter sans réfléchir ce que font les autres n'est pas raisonnable, car vous pouvez aussi adopter des erreurs. Des erreurs dont l'autre aimerait peut-être beaucoup se débarrasser lui-même, mais c'est trop difficile.
Je ne reconnais pas dans le code ce qui est une interface
De nombreux programmeurs objecteront que les préfixes/suffixes leur sont utiles, car grâce à eux, ils reconnaissent immédiatement dans le code ce qui sont des interfaces. Ils ont l'impression qu'une telle distinction leur manquerait. Voyons voir, reconnaissez-vous dans ces exemples ce qui est une classe et ce qui est une interface ?
$o = new X;
class X extends X implements Y
{}
interface Y
{}
X::fn();
X::$v;
X
est toujours une classe, Y
est une interface,
c'est sans ambiguïté même sans préfixes/postfixes. Bien sûr, l'IDE le sait
aussi et dans le contexte donné, il vous suggérera toujours correctement.
Mais qu'en est-il ici :
function foo(A $param): A
{}
public A $property;
$o instanceof A
A::CONST
try {
} catch (A $x) {
}
Dans ces cas, vous ne le reconnaîtrez pas. Comme nous l'avons dit au tout début, il ne devrait pas y avoir de différence du point de vue du développeur entre ce qui est une classe et ce qui est une interface. C'est précisément ce qui donne un sens aux interfaces et aux classes abstraites.
Si vous étiez capable de distinguer ici une classe d'une interface, cela nierait le principe fondamental de la POO. Et les interfaces perdraient leur sens.
J'y suis habitué
Changer ses habitudes fait mal, tout simplement 🙂 Combien de fois même l'idée. Mais ne soyons pas injustes, de nombreuses personnes sont au contraire attirées par les changements et s'en réjouissent, mais pour la plupart, l'habitude est une seconde nature.
Mais il suffit de regarder dans le passé, comment certaines habitudes ont
été emportées par le temps. La plus célèbre est probablement la notation
hongroise utilisée depuis les années 80 et popularisée par Microsoft. La
notation consistait en ce que le nom de chaque variable commençait par une
abréviation symbolisant son type de données. En PHP, cela ressemblerait à
ceci echo $this->strName
ou $this->intCount++
.
On a commencé à abandonner la notation hongroise dans les années 90 et
aujourd'hui, Microsoft dans ses directives décourage directement les
développeurs de l'utiliser.
Autrefois, c'était indispensable et aujourd'hui, cela ne manque à personne.
Mais pourquoi remonter si loin dans le passé ? Vous vous souvenez peut-être qu'en PHP, il était d'usage de distinguer les membres non publics des classes par un trait de soulignement (exemple de Zend Framework). C'était à l'époque où PHP 5 existait depuis longtemps, qui avait les modificateurs de visibilité public/protected/private. Mais les programmeurs le faisaient par habitude. Ils étaient convaincus que sans les traits de soulignement, ils cesseraient de s'orienter dans le code. « Comment reconnaîtrais-je dans le code les variables publiques des privées, hein ? »
Aujourd'hui, personne n'utilise les traits de soulignement. Et ils ne manquent à personne. Le temps a parfaitement prouvé que les craintes étaient vaines.
Pourtant, c'est exactement la même chose que l'objection : « Comment reconnaîtrais-je dans le code une interface d'une classe, hein ? »
J'ai arrêté d'utiliser les préfixes/postfixes il y a dix ans. Je ne reviendrais jamais en arrière, c'était une excellente décision. Je ne connais aucun autre programmeur qui voudrait revenir en arrière. Comme l'a dit un ami : « Essayez-le et dans un mois, vous ne comprendrez pas comment vous avez pu faire autrement. »
Je veux maintenir la cohérence
Je peux imaginer qu'un programmeur se dise : « Utiliser des préfixes et des suffixes est vraiment absurde, je le comprends, mais j'ai déjà construit mon code comme ça et le changement est très difficile. Et si je commençais à écrire le nouveau code correctement sans eux, j'aurais une incohérence, ce qui est peut-être encore pire qu'une mauvaise convention. »
En réalité, votre code est déjà incohérent, car vous utilisez la bibliothèque système PHP, qui n'a pas de préfixes ni de postfixes :
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
public function add(string|Stringable $item): void
{
}
}
Et honnêtement, est-ce que ça dérange ? Avez-vous déjà pensé que ce serait plus cohérent comme ça ?
class Collection implements ArrayAccessInterface, CountableInterface, IteratorAggregateInterface
{
public function add(string|StringableInterface $item): void
{
}
}
Ou ça ?
try {
$command = $this->find($name);
} catch (ThrowableInterface $e) {
return $e->getMessage();
}
Je ne pense pas. La cohérence ne joue pas un rôle aussi important qu'il pourrait y paraître. Au contraire, l'œil préfère moins de bruit visuel, le cerveau la pureté de la conception. Donc, modifier la convention et commencer à écrire de nouvelles interfaces correctement sans préfixes ni suffixes a du sens.
Il est possible de les supprimer délibérément même dans de grands
projets. Un exemple est Nette Framework, qui utilisait historiquement les
préfixes I
dans les noms d'interface, dont il a commencé à se débarrasser
progressivement et en conservant pleinement la compatibilité ascendante il
y a quelques années.
Pour soumettre un commentaire, veuillez vous connecter