Prefissi e suffissi non appartengono ai nomi delle interfacce
L'uso del prefisso I
o del suffisso Interface
per
le interfacce, così come Abstract
per le classi astratte, è un
antipattern. Non ha posto nel codice pulito. La distinzione dei nomi delle
interfacce in realtà oscura i principi OOP, introduce rumore nel codice e
causa complicazioni nello sviluppo. Le ragioni sono le seguenti.

Tipo = classe + interfaccia + discendenti
Nel mondo OOP, sia le classi che le interfacce sono considerate tipi. Se uso un tipo nella dichiarazione di una proprietà o di un parametro, dal punto di vista dello sviluppatore non c'è differenza se il tipo su cui si fa affidamento è una classe o un'interfaccia. Questa è una cosa fantastica, grazie alla quale le interfacce sono in realtà così utili. Questo dà senso alla loro esistenza. (Seriamente: a cosa servirebbero le interfacce se questo principio non fosse valido? Provate a pensarci.)
Guardate questo codice:
class FormRenderer
{
public function __construct(
private Form $form,
private Translator $translator,
) {
}
}
Il costruttore dice: “Ho bisogno di un form e di un traduttore.”
E non gli importa affatto se riceve un oggetto GettextTranslator
o
DatabaseTranslator
. E allo stesso tempo, come utente, non mi
importa affatto se Translator
è un'interfaccia, una classe
astratta o una classe concreta.
Non mi importa affatto? In realtà no, ammetto di essere piuttosto curioso, quindi quando esamino una libreria altrui, do un'occhiata a cosa si nasconde dietro il tipo e ci passo sopra il mouse:

Ah, ora lo so. E finisce qui. La conoscenza se si tratta di una classe o di un'interfaccia sarebbe importante se volessi creare la sua istanza, ma non è questo il caso, ora sto solo parlando del tipo della variabile. E qui voglio essere schermato da questi dettagli. E non voglio assolutamente introdurli nel mio codice! Ciò che si nasconde dietro il tipo fa parte della sua definizione, non del tipo stesso.
E ora guardate il codice successivo:
class FormRenderer
{
public function __construct(
private AbstractForm $form,
private TranslatorInterface $translator,
) {
}
}
Questa definizione del costruttore dice letteralmente: “Ho bisogno di un form astratto e di un'interfaccia traduttore.” Ma questa è una sciocchezza. Ha bisogno di un form concreto da renderizzare. Non di un form astratto. E ha bisogno di un oggetto che svolga il ruolo di traduttore. Non ha bisogno di un'interfaccia.
Voi sapete che le parole Interface
e Abstract
devono essere ignorate. Che il costruttore vuole la stessa cosa dell'esempio
precedente. Ma… sul serio? Vi sembra davvero una buona idea introdurre nelle
convenzioni di denominazione l'uso di parole che devono essere trascurate?
Crea una falsa idea dei principi OOP. Un principiante deve essere confuso:
“Se per tipo Translator
si intende 1) un oggetto della classe
Translator
2) un oggetto che implementa l'interfaccia
Translator
o 3) un oggetto che ne eredita, cosa si intende allora
per TranslatorInterface
?” A questo non si può rispondere
ragionevolmente.
Quando scriviamo TranslatorInterface
, sebbene anche
Translator
possa essere un'interfaccia, commettiamo una tautologia.
Lo stesso quando dichiariamo interface TranslatorInterface
. E così
via. Fino a creare uno scherzo da programmatori:
interface TranslatorInterface
{
}
class FormRendererClass
{
/**
* Costruttore
*/
public function __construct(
private AbstractForm $privatePropertyForm,
private TranslatorInterface $privatePropertyTranslator,
) {
// 🤷♂️
}
}
Implementazione eccezionale
Quando vedo qualcosa come TranslatorInterface
, è probabile che
esista anche un'implementazione con il nome
Translator implements TranslatorInterface
. Mi costringe a
riflettere: cosa rende Translator
così eccezionale da avere il
diritto unico di chiamarsi Translator? Ogni altra implementazione necessita di
un nome descrittivo, ad esempio GettextTranslator
o
DatabaseTranslator
, ma questa è in qualche modo “predefinita”,
come suggerisce la sua posizione privilegiata, chiamandosi
Translator
senza aggettivi.
Questo addirittura rende le persone insicure e non sanno se devono scrivere
il typehint per Translator
o TranslatorInterface
. Nel
codice client si mescolano quindi entrambi, sicuramente vi siete già imbattuti
molte volte (in Nette ad esempio in relazione a Nette\Http\Request
vs IRequest
).
Non sarebbe meglio sbarazzarsi dell'implementazione eccezionale e lasciare il
nome generico Translator
per l'interfaccia? Cioè avere
implementazioni concrete con un nome concreto + un'interfaccia generica con un
nome generico. Questo ha senso.
L'onere del nome descrittivo ricade quindi puramente sulle implementazioni.
Se rinominiamo TranslatorInterface
in Translator
, la
nostra ex classe Translator
ha bisogno di un nuovo nome. Le persone
tendono a risolvere questo problema chiamandola DefaultTranslator
,
anch'io sono colpevole. Ma ancora, cosa la rende così eccezionale da chiamarsi
Default? Non siate pigri e pensate bene a cosa fa e perché si differenzia dalle
altre possibili implementazioni.
E se non riesco a immaginare più implementazioni? E se mi viene in mente solo un modo valido? Allora semplicemente non create l'interfaccia. Almeno per ora.
Ecco, è apparsa un'altra implementazione
Ed eccoci qui! Abbiamo bisogno di una seconda implementazione. Succede comunemente. Non c'è mai stata la necessità di memorizzare le traduzioni in un modo diverso da un unico metodo collaudato, ad esempio in un database, ma ora è emersa una nuova esigenza ed è necessario avere più traduttori nell'applicazione.
Questo è anche il momento in cui vi rendete chiaramente conto di quale fosse la specificità del traduttore unico originale. Era un traduttore basato su database, non predefinito.
Cosa fare?
- Trasformiamo il nome
Translator
in un'interfaccia - Rinominate la classe originale in
DatabaseTranslator
e implementeràTranslator
- E create nuove classi
GettextTranslator
e magariNeonTranslator
Tutte queste modifiche si fanno molto comodamente e facilmente, soprattutto
se l'applicazione è costruita secondo i principi della dependency
injection. Non è necessario modificare nulla nel codice, solo nella
configurazione del container DI cambiamo Translator
in
DatabaseTranslator
. Questo è fantastico!
Una situazione diametralmente diversa si verificherebbe però se insistessimo
sui prefissi/suffissi. Dovremmo rinominare i tipi in tutto il codice
dell'applicazione da Translator
a TranslatorInterface
.
Tale ridenominazione sarebbe puramente finalizzata al rispetto della
convenzione, ma andrebbe contro il senso dell'OOP, come abbiamo mostrato poco
fa. L'interfaccia non è cambiata, il codice utente non è cambiato, ma la
convenzione richiede di rinominare? Allora è una convenzione errata.
Se inoltre col tempo si scoprisse che una classe astratta sarebbe migliore di un'interfaccia, rinomineremmo di nuovo. Un tale intervento non è affatto banale, ad esempio se il codice è distribuito su più pacchetti o viene utilizzato da terze parti.
Ma tutti fanno così
Non tutti. È vero che nel mondo PHP la distinzione dei nomi delle interfacce e delle classi astratte è stata resa popolare da Zend Framework e poi da Symfony, cioè grandi player. Questo approccio è stato adottato anche da PSR, che paradossalmente pubblica solo interfacce, eppure per ognuna riporta nel nome la parola interfaccia.
D'altra parte, un altro framework significativo, Laravel, non distingue
affatto interfacce e classi astratte. Non lo fa ad esempio nemmeno il popolare
layer di database Doctrine. E non lo fa nemmeno la libreria standard di PHP
(abbiamo così l'interfaccia Throwable
o Iterator
, la
classe astratta FilterIterator
, ecc.).
Se guardassimo al mondo al di fuori di PHP, ad esempio C# utilizza il
prefisso I
per le interfacce, al contrario in Java o TypeScript
i nomi non vengono distinti.
Quindi non lo fanno tutti, tuttavia anche se lo facessero, non significa che sia giusto. Adottare acriticamente ciò che fanno gli altri non è ragionevole, perché si possono adottare anche errori. Errori di cui l'altro molto probabilmente si libererebbe volentieri, solo che è troppo impegnativo.
Non riconosco nel codice cos'è un'interfaccia
Molti programmatori obietteranno che per loro prefissi/suffissi sono utili, perché grazie ad essi riconoscono subito nel codice cosa sono le interfacce. Hanno la sensazione che tale distinzione mancherebbe loro. Vediamo un po', riconoscete in questi esempi cosa è una classe e cosa un'interfaccia?
$o = new X;
class X extends X implements Y
{}
interface Y
{}
X::fn();
X::$v;
X
è sempre una classe, Y
è un'interfaccia, è
inequivocabile anche senza prefissi/suffissi. Ovviamente lo sa anche l'IDE e nel
contesto dato vi suggerirà sempre correttamente.
Ma cosa succede qui:
function foo(A $param): A
{}
public A $property;
$o instanceof A
A::CONST
try { ... } catch (A $x) { ... }
In questi casi non lo riconoscete. Come abbiamo detto all'inizio, qui dal punto di vista dello sviluppatore non dovrebbe esserci differenza tra cosa è una classe e cosa un'interfaccia. Il che appunto dà senso alle interfacce e alle classi astratte.
Se qui foste in grado di distinguere una classe da un'interfaccia, neghereste il principio fondamentale dell'OOP. E le interfacce perderebbero il loro senso.
Ci sono abituato
Cambiare le abitudini fa semplicemente male 🙂 Quante volte anche solo l'idea. Ma per non essere ingiusti, molte persone sono invece attratte dai cambiamenti e non vedono l'ora, tuttavia per la maggior parte vale che l'abitudine è una camicia di ferro.
Ma basta guardare al passato, come alcune abitudini siano state spazzate via
dal tempo. Probabilmente la più famosa è la cosiddetta notazione ungherese
usata dagli anni Ottanta e resa popolare da Microsoft. La notazione consisteva
nel fatto che il nome di ogni variabile iniziava con una sigla che simboleggiava
il suo tipo di dati. In PHP apparirebbe così
echo $this->strName
o $this->intCount++
. Dalla
notazione ungherese si è iniziato ad abbandonare negli anni Novanta e oggi
Microsoft nelle sue linee guida scoraggia direttamente gli sviluppatori dal suo
utilizzo.
Un tempo era imprescindibile e oggi non manca a nessuno.
Ma perché andare così indietro nel tempo? Forse ricordate che in PHP era consuetudine distinguere i membri non pubblici delle classi con un trattino basso (esempio da Zend Framework). Era ai tempi in cui esisteva già da tempo PHP 5, che aveva i modificatori di visibilità public/protected/private. Ma i programmatori lo facevano per abitudine. Erano convinti che senza i trattini bassi avrebbero smesso di orientarsi nel codice. “Come distinguerei nel codice le variabili pubbliche da quelle private, eh?”
Oggi nessuno usa i trattini bassi. E non mancano a nessuno. Il tempo ha dimostrato brillantemente che le preoccupazioni erano infondate.
Eppure è esattamente la stessa obiezione: “Come distinguerei nel codice un'interfaccia da una classe, eh?”
Ho smesso di usare prefissi/suffissi dieci anni fa. Non tornerei mai indietro, è stata una decisione fantastica. Non conosco nemmeno nessun altro programmatore che vorrebbe tornare indietro. Come ha detto un amico: “Provalo e tra un mese non capirai come hai fatto a fare diversamente.”
Voglio mantenere la coerenza
Posso immaginare che un programmatore si dica: “Usare prefissi e suffissi è davvero un nonsenso, lo capisco, ma ho già costruito il codice così e il cambiamento è molto difficile. E se iniziassi a scrivere il nuovo codice correttamente senza di essi, creerei un'incoerenza, che è forse peggiore della cattiva convenzione.”
In realtà, il vostro codice è già incoerente ora, perché utilizzate la libreria di sistema di PHP, che non ha prefissi e suffissi:
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
public function add(string|Stringable $item): void
{
}
}
E mano sul cuore, dà fastidio? Vi è mai venuto in mente che sarebbe stato più coerente questo?
class Collection implements ArrayAccessInterface, CountableInterface, IteratorAggregateInterface
{
public function add(string|StringableInterface $item): void
{
}
}
O questo?
try {
$command = $this->find($name);
} catch (ThrowableInterface $e) {
return $e->getMessage();
}
Penso di no. La coerenza non gioca un ruolo così significativo come potrebbe sembrare. Al contrario, l'occhio preferisce meno rumore visivo, il cervello la pulizia del design. Quindi modificare la convenzione e iniziare a scrivere nuove interfacce correttamente senza prefissi e suffissi ha senso.
Possono essere eliminati di proposito anche da progetti di grandi dimensioni. Un esempio è il Nette Framework, che storicamente utilizzava i prefissi I nei nomi delle interfacce, che ha iniziato a eliminare gradualmente alcuni anni fa, mantenendo la piena compatibilità con il passato.
Per inviare un commento, effettuare il login