Prefixes and Suffixes Do Not Belong in Interface Names
Using the I
prefix or Interface
suffix for
interfaces, as well as Abstract
for abstract classes, is an
anti-pattern. It has no place in clean code. Differentiating interface names, in
fact, obscures OOP principles, introduces noise into the code, and complicates
development. Here are the reasons.
Type = Class + Interface + Descendants
In the world of OOP, both classes and interfaces are considered types. When using a type in a property or parameter declaration, there is no difference from a developer's perspective between whether the type being relied upon is a class or an interface. This is a fantastic feature that makes interfaces so useful. It gives their existence meaning. (Seriously: what would be the point of interfaces if this principle didn't apply? Try to think about it.)
Take a look at this code:
class FormRenderer
{
public function __construct(
private Form $form,
private Translator $translator,
) {
}
}
The constructor says: “I need a form and a translator.” And it
doesn't care if it gets a GettextTranslator
object or a
DatabaseTranslator
object. And at the same time, I as a user don't
care if Translator
is an interface, an abstract class or a
concrete class.
Do I really not care? Well, actually, I admit I'm quite curious, so when I'm exploring someone else's library, I take a peek at what's behind the type and hover over it with my mouse:
Aha, now I know. And that's the end of the story. Knowing whether it's a class or an interface would be important if I wanted to create an instance of it, but that's not the case, I'm just talking about the type of the variable now. And here, I want to be shielded from these details. And I certainly don't want them cluttering my code! What's behind a type is part of its definition, not the type itself.
Now, take a look at the other code:
class FormRenderer
{
public function __construct(
private AbstractForm $form,
private TranslatorInterface $translator,
) {
}
}
This constructor definition literally says: “I need an abstract form and a translator interface.” But that's nonsense. It needs a concrete form to render. Not an abstract form. And it needs an object that fulfills the role of a translator. It doesn't need an interface.
You know that the words Interface
and Abstract
should be ignored. That the constructor wants the same thing as in the previous
example. But… seriously? Does it really seem like a good idea to introduce the
use of words in naming conventions that should be overlooked?
It creates a false understanding of OOP principles. A beginner must be
confused: “If the Translator
type means either 1) an object of
the Translator
class, 2) an object implementing the
Translator
interface, or 3) an object inheriting from them, what
does TranslatorInterface
mean then?” There is no reasonable
answer to this.
When we write TranslatorInterface
, even though
Translator
can also be an interface, we are committing a tautology.
The same goes for declaring interface TranslatorInterface
. And so
on. Until a programmer's joke is created:
interface TranslatorInterface
{
}
class FormRendererClass
{
/**
* Constructor
*/
public function __construct(
private AbstractForm $privatePropertyForm,
private TranslatorInterface $privatePropertyTranslator,
) {
// 🤷♂️
}
}
Outstanding Implementation
When I see something like TranslatorInterface
, it's likely
that there will also be an implementation named
Translator implements TranslatorInterface
. It makes me wonder: what
makes Translator
so special that it has the unique right to be
called Translator? Every other implementation needs a descriptive name, like
GettextTranslator
or DatabaseTranslator
, but this one
is somehow “default”, as its privileged position suggests when it's named
Translator
without any label.
It even makes people uncertain about whether to write a type hint for
Translator
or TranslatorInterface
. In client code,
both are mixed, and you've undoubtedly encountered this many times (in Nette,
for example, in connection with Nette\Http\Request
vs
IRequest
).
Wouldn't it be better to get rid of the outstanding implementation and leave
the generic name Translator
for the interface? That is, having
specific implementations with specific names + a generic interface with a
generic name. That makes sense.
The burden of descriptive names then lies purely on the implementations. If
we rename TranslatorInterface
to Translator
, our
former class Translator
needs a new name. People tend to solve this
problem by calling it DefaultTranslator
, and I am guilty of this
too. But again, what makes it so special that it's named Default? Don't be lazy
and think carefully about what it does and why it differs from other possible
implementations.
And what if I can't imagine more than one implementation? What if I can only think of one valid way? Then simply don't create an interface. At least for now.
Eh, There's Another Implementation
And here it is! We need a second implementation. It happens all the time. There was never a need to store translations in any other way than one proven method, e.g., in a database, but now a new requirement has emerged, and we need to have multiple translators in the application.
This is also the moment when you clearly realize the specificity of the original single translator. It was a database translator, not a default.
What to do with it?
- Make the name
Translator
the interface - Rename the original class to
DatabaseTranslator
and have it implementTranslator
- Create new classes
GettextTranslator
and maybeNeonTranslator
All of these changes can be made very comfortably and easily, especially if
the application is built in accordance with dependency
injection principles. There's no need to change anything in the
code, just change Translator
to DatabaseTranslator
in the DI container configuration. That's great!
A completely different situation would arise if we insisted on
prefixing/suffixing. We would have to rename types across the application from
Translator
to TranslatorInterface
. Such renaming would
be purely purposeful to comply with the convention but would go against the
sense of OOP, as we demonstrated earlier. The interface hasn't changed, the user
code hasn't changed, but the convention requires renaming? Then it's a flawed
convention.
Moreover, if it turned out over time that an abstract class would be better than an interface, we would rename again. Such an action may not be trivial, especially when the code is spread across multiple packages or used by third parties.
But Everybody Does It
Not everyone. It's true that in the world of PHP, the differentiation of interface and abstract class names was popularized by Zend Framework and later Symfony, the big players. This approach was also adopted by PSR, which paradoxically publishes only interfaces, yet specifies the word interface in the name of each one.
On the other hand, another significant framework, Laravel, does not
distinguish between interfaces and abstract classes. Nor does the popular
database layer Doctrine, for example. And neither does the PHP standard library
(we have interfaces like Throwable
or Iterator
,
abstract classes like FilterIterator
, etc).
If we looked at the world outside PHP, for example, C# uses the prefix
I
for interfaces, while Java or TypeScript do not
differentiate names.
So not everyone does it, but even if they did, it doesn't mean it's the right way. Mindlessly adopting what others do is not wise because you may adopt their mistakes too. Mistakes that the other party would very likely want to get rid of, but it's too demanding.
I Can't Tell in the Code What Is an Interface
Many programmers will argue that prefixes/suffixes are useful for them because they can immediately tell in the code what is an interface. They feel like they would miss such a distinction. So let's see if you can tell in these examples what is a class and what is an interface:
$o = new X;
class X extends X implements Y
{}
interface Y
{}
X::fn();
X::$v;
X
is always a class, Y
is an interface, and it is
unambiguous even without prefixes/postfixes. Of course, your IDE also knows this
and will always suggest correctly in the given context.
But what about here:
function foo(A $param): A
{}
public A $property;
$o instanceof A
A::CONST
try {
} catch (A $x) {
}
In these cases, you can't tell. As we said at the very beginning, from a developer's perspective, there should be no difference between what is a class and what is an interface. That's what gives interfaces and abstract classes their purpose.
If you were able to distinguish a class from an interface here, it would contradict the basic principle of OOP. And interfaces would lose their meaning.
I'm Used to It
Changing habits simply hurts 🙂 Many times, even the idea of it hurts. However, let's not be unfair, many people are actually attracted by changes and look forward to it. Nevertheless, for the majority, there is a Czech proverb that says, “habit is an iron shirt.”
But looking back in time shows how some customs have been blown away. Perhaps
the most infamous is the so-called Hungarian notation used since the 1980s and
popularized by Microsoft. The notation consisted of starting the name of each
variable with an abbreviation symbolizing its data type. In PHP, it would look
like this: echo $this->strName
or
$this->intCount++
. The Hungarian notation began to fade in the
1990s, and today Microsoft explicitly discourages its use in its developer
guidelines.
Once it was indispensable, and now no one misses it.
But why go so far back? You may remember that in PHP, it was customary to distinguish non-public class members with an underscore (an example from Zend Framework). It was at a time when PHP 5 already existed, which had public/protected/private visibility modifiers. But programmers did it out of habit. They were convinced that without underscores, they would lose orientation in the code. “How would I tell public from private variables in the code, huh?”
Today, no one uses underscores. And no one misses them. Time has wonderfully proven that the fears were unfounded.
Yet it's exactly the same as the objection: “How would I tell an interface from a class in the code, huh?”
I stopped using prefixes/postfixes ten years ago. I would never go back; it was a great decision. I don't know any other programmer who would want to go back. As a friend said, “Try it, and in a month, you won't understand how you ever did it differently.”
I Want to Maintain Consistency
I can imagine a programmer saying, “Using prefixes and suffixes is really nonsense, I get it, but I already have code built this way, and changing it is very difficult. And if I started writing new code correctly without them, I would create inconsistency, which is perhaps even worse than a bad convention.”
In fact, your code is already inconsistent because you are using the PHP system library, which has no prefixes or postfixes:
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
public function add(string|Stringable $item): void
{
}
}
And honestly, does it bother you? Have you ever thought it would be more consistent to have this?
class Collection implements ArrayAccessInterface, CountableInterface, IteratorAggregateInterface
{
public function add(string|StringableInterface $item): void
{
}
}
Or this?
try {
$command = $this->find($name);
} catch (ThrowableInterface $e) {
return $e->getMessage();
}
I don't think so. Consistency doesn't play as significant a role as it might seem. On the contrary, the eye prefers less visual noise, and the brain prefers a clean design. Therefore, adjusting the convention and starting to write new interfaces correctly without prefixes and suffixes makes sense.
You can deliberately remove them even from large projects. An example is the
Nette Framework, which historically used the I
prefix in interface
names, which it started
to phase out a few years ago, while maintaining full backward
compatibility.
(One of the inspirational sources for the argument was the article Sensible Interfaces by Mathias Verraes)
Comments
Nice article. Same applies to every Exception suffix for exception/throwable classes in PHP and also Trait suffix for traits.
Because removing Trait word from ex. UserTrait will nicely show you, that you missing some proper name.
The example with renaming types across the application from Translator to TranslatorInterface got me. I am converted now.
Sign in to submit a comment