Los prefijos y sufijos no pertenecen a los nombres de las interfaces
Usar el prefijo I
o el sufijo Interface
en las
interfaces, así como Abstract
en las clases abstractas, es un
antipatrón. No tiene cabida en el código limpio. Distinguir los nombres de las
interfaces en realidad oscurece los principios de la POO, introduce ruido en el
código y causa complicaciones en el desarrollo. Las razones son las
siguientes.

Tipo = clase + interfaz + descendientes
En el mundo de la POO, tanto las clases como las interfaces se consideran tipos. Si utilizo un tipo al declarar una propiedad o un parámetro, desde el punto de vista del desarrollador no hay diferencia entre si el tipo en el que confía es una clase o una interfaz. Esto es algo genial, gracias a lo cual las interfaces son realmente tan útiles. Esto da sentido a su existencia. (En serio: ¿para qué servirían las interfaces si este principio no se aplicara? Intente reflexionar sobre ello.)
Observe este código:
class FormRenderer
{
public function __construct(
private Form $form,
private Translator $translator,
) {
}
}
El constructor dice: “Necesito un formulario y un traductor.” Y
le da completamente igual si recibe un objeto GettextTranslator
o
DatabaseTranslator
. Y al mismo tiempo, como usuario, me da
completamente igual si Translator
es una interfaz, una clase
abstracta o una clase concreta.
¿Me da completamente igual? En realidad no, confieso que soy bastante curioso, así que cuando examino una librería externa, echo un vistazo a lo que se esconde detrás del tipo y paso el ratón sobre él:

Ah, ya lo sé. Y ahí termina. El conocimiento de si se trata de una clase o una interfaz sería importante si quisiera crear su instancia, pero no es el caso, ahora solo hablo del tipo de la variable. Y aquí quiero estar abstraído de estos detalles. ¡Y mucho menos quiero introducirlos en mi código! Lo que se esconde detrás del tipo es parte de su definición, no del tipo en sí.
Y ahora mire el siguiente código:
class FormRenderer
{
public function __construct(
private AbstractForm $form,
private TranslatorInterface $translator,
) {
}
}
Esta definición del constructor dice literalmente: “Necesito un formulario abstracto y una interfaz de traductor.” Pero eso es una tontería. Necesita un formulario concreto que tiene que renderizar. No un formulario abstracto. Y necesita un objeto que cumpla la función de traductor. No necesita una interfaz.
Usted sabe que las palabras Interface
y Abstract
deben ignorarse. Que el constructor quiere lo mismo que en el ejemplo anterior.
Pero… ¿en serio? ¿Realmente le parece una buena idea introducir en las
convenciones de nombres el uso de palabras que deben pasarse por alto?
Porque crea una idea falsa sobre los principios de la POO. Un principiante
debe estar confundido: “Si por el tipo Translator
se entiende 1)
un objeto de la clase Translator
, 2) un objeto que implementa la
interfaz Translator
o 3) un objeto que hereda de ellos, ¿qué se
entiende entonces por TranslatorInterface
?” A esto no se puede
responder razonablemente.
Cuando escribimos TranslatorInterface
, aunque
Translator
también puede ser una interfaz, cometemos una
tautología. Lo mismo cuando declaramos
interface TranslatorInterface
. Y así sucesivamente. Hasta que
surge un chiste de programador:
interface TranslatorInterface
{
}
class FormRendererClass
{
/**
* Constructor
*/
public function __construct(
private AbstractForm $privatePropertyForm,
private TranslatorInterface $privatePropertyTranslator,
) {
// 🤷♂️
}
}
Implementación excepcional
Cuando veo algo como TranslatorInterface
, es probable que
también exista una implementación con el nombre
Translator implements TranslatorInterface
. Me obliga a reflexionar:
¿qué hace que Translator
sea tan excepcional como para tener el
derecho único de llamarse Translator? Cualquier otra implementación necesita
un nombre descriptivo, por ejemplo GettextTranslator
o
DatabaseTranslator
, pero esta es de alguna manera
“predeterminada”, como sugiere su posición preferente al llamarse
Translator
sin calificativo.
Incluso confunde a la gente y no saben si deben escribir el typehint para
Translator
o TranslatorInterface
. En el código
cliente, entonces se mezcla ambos, seguro que se ha encontrado con esto muchas
veces (en Nette, por ejemplo, en relación con Nette\Http\Request
vs IRequest
).
¿No sería mejor deshacerse de la implementación excepcional y dejar el
nombre genérico Translator
para la interfaz? Es decir, tener
implementaciones concretas con un nombre concreto + una interfaz genérica con
un nombre genérico. Eso tiene sentido, ¿verdad?
La carga del nombre descriptivo recae entonces puramente en las
implementaciones. Si renombramos TranslatorInterface
a
Translator
, nuestra antigua clase Translator
necesita
un nuevo nombre. La gente tiende a resolver este problema llamándola
DefaultTranslator
, yo también soy culpable. Pero de nuevo, ¿qué
la hace tan excepcional como para llamarse Default? No sea perezoso y reflexione
bien sobre lo que hace y por qué se diferencia de otras posibles
implementaciones.
¿Y qué pasa si no puedo imaginar múltiples implementaciones? ¿Qué pasa si solo se me ocurre una forma válida? Entonces simplemente no cree la interfaz. Al menos por ahora.
¡Vaya, apareció otra implementación!
¡Y aquí está! Necesitamos una segunda implementación. Sucede comúnmente. Nunca surgió la necesidad de almacenar traducciones de otra manera que no fuera una forma probada, por ejemplo, en una base de datos, pero ahora ha aparecido un nuevo requisito y es necesario tener más traductores en la aplicación.
Este es también el momento en que se da cuenta claramente de cuál era la especificidad del traductor único original. Era un traductor de base de datos, no uno predeterminado.
¿Qué hacer al respecto?
- Hacemos que el nombre
Translator
sea una interfaz - Renombramos la clase original a
DatabaseTranslator
y hará que implementeTranslator
- Y creamos nuevas clases
GettextTranslator
y quizásNeonTranslator
Todos estos cambios se realizan de manera muy cómoda y fácil, especialmente
si la aplicación está construida de acuerdo con los principios de inyección de
dependencias. No es necesario cambiar nada en el código, solo en la
configuración del contenedor DI cambiamos Translator
por
DatabaseTranslator
. ¡Eso es genial!
Una situación diametralmente diferente ocurriría, sin embargo, si
insistiéramos en prefijar/sufijar. Tendríamos que renombrar los tipos en todo
el código de la aplicación de Translator
a
TranslatorInterface
. Tal renombramiento sería puramente
intencionado para cumplir con la convención, pero iría en contra del sentido
de la POO, como mostramos hace un momento. La interfaz no cambió, el código de
usuario no cambió, ¿pero la convención requiere renombrar? Entonces es una
convención errónea.
Si además, con el tiempo, resultara que una clase abstracta sería mejor que una interfaz, volveríamos a renombrar. Tal intervención no tiene por qué ser trivial en absoluto, por ejemplo, si el código está distribuido en varios paquetes o es utilizado por terceros.
Pero todo el mundo lo hace así
No todo el mundo. Es cierto que en el mundo de PHP, la distinción de nombres de interfaces y clases abstractas fue popularizada por Zend Framework y después por Symfony, es decir, grandes actores. Este enfoque adoptado también por PSR, que paradójicamente publica solo interfaces y, sin embargo, incluye en el nombre de cada una la palabra interfaz.
Por otro lado, otro framework importante, Laravel, no distingue de ninguna
manera las interfaces y las clases abstractas. Tampoco lo hace, por ejemplo, la
popular capa de base de datos Doctrine. Y tampoco lo hace la biblioteca
estándar de PHP (tenemos así la interfaz Throwable
o
Iterator
, la clase abstracta
FilterIterator
, etc.).
Si miramos fuera del mundo de PHP, por ejemplo, C# utiliza el prefijo
I
para las interfaces, mientras que en Java o TypeScript los
nombres no se distinguen.
Por lo tanto, no todo el mundo lo hace, sin embargo, incluso si lo hicieran, no significa que sea lo correcto. Adoptar sin pensar lo que hacen los demás no es razonable, porque también puede adoptar errores. Errores de los que el otro muy probablemente se desharía él mismo, solo que es demasiado exigente.
No reconozco en el código qué es una interfaz
Muchos programadores argumentarán que los prefijos/sufijos les son útiles, ya que gracias a ellos reconocen inmediatamente en el código qué son interfaces. Tienen la sensación de que les faltaría tal distinción. A ver, ¿reconoce en estos ejemplos qué es una clase y qué una interfaz?
$o = new X;
class X extends X implements Y
{}
interface Y
{}
X::fn();
X::$v;
X
es siempre una clase, Y
es una interfaz, es
inequívoco incluso sin prefijos/sufijos. Por supuesto, el IDE también lo sabe
y en el contexto dado siempre le sugerirá correctamente.
Pero, ¿y aquí?
function foo(A $param): A
{}
public A $property;
$o instanceof A
A::CONST
try {
} catch (A $x) {
}
En estos casos no lo reconocerá. Como dijimos al principio, aquí no debe haber diferencia desde el punto de vista del desarrollador entre qué es una clase y qué una interfaz. Lo cual precisamente da sentido a las interfaces y clases abstractas.
Si aquí fuera capaz de distinguir una clase de una interfaz, negaría el principio fundamental de la POO. Y las interfaces perderían su sentido.
Estoy acostumbrado a ello
Cambiar las costumbres simplemente duele 🙂 Cuántas veces incluso la sola idea. Pero para no ser injustos, a muchas personas, por el contrario, los cambios les atraen y los esperan con ilusión, sin embargo, para la mayoría aplica que la costumbre es una camisa de fuerza.
Pero basta con mirar al pasado, cómo algunas costumbres se las llevó el
tiempo. Probablemente la más famosa es la llamada notación húngara utilizada
desde los años ochenta y popularizada por Microsoft. La notación consistía en
que el nombre de cada variable comenzaba con una abreviatura que simbolizaba su
tipo de dato. En PHP se vería así echo $this->strName
o
$this->intCount++
. La notación húngara comenzó a abandonarse
en los años noventa y hoy Microsoft en sus directrices disuade directamente a
los desarrolladores de usarla.
Antaño era indispensable y hoy nadie la echa de menos.
Pero, ¿por qué ir a un pasado tan lejano? Quizás recuerde que en PHP era costumbre distinguir los miembros no públicos de las clases con un guion bajo (ejemplo de Zend Framework). Fue en la época en que ya existía PHP 5, que tenía modificadores de visibilidad public/protected/private. Pero los programadores lo hacían por costumbre. Estaban convencidos de que sin guiones bajos dejarían de orientarse en el código. “¿Cómo distinguiría en el código las variables públicas de las privadas, eh?”
Hoy nadie usa guiones bajos. Y nadie los echa de menos. El tiempo ha demostrado perfectamente que las preocupaciones eran infundadas.
Sin embargo, es exactamente lo mismo que la objeción: “¿Cómo distinguiría en el código una interfaz de una clase, eh?”
Dejé de usar prefijos/sufijos hace diez años. Nunca volvería, fue una decisión genial. Tampoco conozco a ningún otro programador que quisiera volver. Como dijo un amigo: “Pruébalo y en un mes no entenderás cómo alguna vez lo hiciste de otra manera.”
Quiero mantener la consistencia
Puedo imaginar que un programador se diga: “Usar prefijos y sufijos es realmente un sinsentido, lo entiendo, pero ya tengo el código estructurado así y el cambio es muy difícil. Y si empezara a escribir el nuevo código correctamente sin ellos, me surgiría una inconsistencia, que es quizás peor que una mala convención.”
En realidad, su código ya es inconsistente ahora, porque usa la biblioteca del sistema de PHP, que no tiene prefijos ni sufijos:
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
public function add(string|Stringable $item): void
{
}
}
Y sinceramente, ¿molesta? ¿Alguna vez ha pensado que esto sería más consistente?
class Collection implements ArrayAccessInterface, CountableInterface, IteratorAggregateInterface
{
public function add(string|StringableInterface $item): void
{
}
}
¿O esto?
try {
$command = $this->find($name);
} catch (ThrowableInterface $e) {
return $e->getMessage();
}
Creo que no. La consistencia no juega un papel tan importante como podría parecer. Por el contrario, el ojo prefiere menos ruido visual, el cerebro la limpieza del diseño. Por lo tanto, ajustar la convención y empezar a escribir nuevas interfaces correctamente sin prefijos y sufijos tiene sentido.
Se pueden eliminar deliberadamente incluso de proyectos grandes. Un ejemplo
es Nette Framework, que históricamente usaba prefijos I
en los
nombres de las interfaces, de lo cual hace unos años comenzó a eliminando
gradualmente y con total preservación de la compatibilidad hacia
atrás.
Para enviar un comentario, inicie sesión