Prefixes and Suffixes Do Not Belong in Interface Names

4 months ago by David Grudl  

Using the I prefix or Interface suffix for interfaces, and Abstract for abstract classes, is an antipattern. It doesn't belong in pure code. Distinguishing interface names actually blurs OOP principles, adds noise to the code, and causes complications while developing. Here are the reasons.

Type = Class + Interface + Descendants

In the OOP world, both classes and interfaces are considered types. If I use a type when declaring a property or a parameter, from the developer's perspective, there is no difference between whether the type they are relying on is a class or an interface. That's the cool thing that makes interfaces so useful, actually. It's what gives their existence meaning. (Seriously: what would interfaces be for if this principle didn't apply?)

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.

I'll admit that 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:

Oh, I see. And that's the end of story. I want to stay out of these implementation details. And I certainly don't want them embedded in my code! What's behind a type is part of its definition, not the type itself.

Now look at the other code:

class FormRenderer
{
	public function __construct(
		private AbstractForm $form,
		private TranslatorInterface $translator,
	) {
	}
}

This definition of constructor literally says: “I need an abstract form and an interface of translator.” But that's silly. It needs a concrete form to render. Not an abstract form. And it needs an object that acts as a translator. It does not need an interface.

You know that the words Interface and Abstract are to be ignored. That the constructor wants the same thing as in the previous example. But… really? Does it really seem like a good idea to introduce into naming conventions the use of words we have to mentally ignore?

After all, it creates a false idea of OOP principles. A beginner must be confused: “If the type Translator means either 1) an object of class Translator 2) an object implementing the Translator interface or 3) an object inheriting from them, then what is meant by TranslatorInterface?” There is no reasonable answer to this.

When we use TranslatorInterface, even if Translator can be an interface, we are committing a tautology. The same happens when we declare interface TranslatorInterface. Gradually, a programming joke will happen:

interface TranslatorInterface
{
}

class FormRendererClass
{
	/**
	 * Constructor
	 */
	public function __construct(
		private AbstractForm $privatePropertyForm,
		private TranslatorInterface $privatePropertyTranslator,
	) {
		// 🤷‍♂️
	}
}

Outstanding Implementation

When I see something like TranslatorInterface, there is likely to be an implementation called Translator implements TranslatorInterface. It makes me wonder: what makes Translator so special that it has the unique privilege of being called Translator? Every other implementation needs a descriptive name, such as GettextTranslator or DatabaseTranslator, but this one is sort of “default”, as suggested by its preferred status of being called Translator without the label.

Even people get confused and don't know whether to typehint for Translator or TranslatorInterface. Then both get mixed up in the client code, I'm sure you've run into this many times (in Nette for example in the connection with Nette\Http\Request vs IRequest).

Wouldn't it be better to get rid of the special implementation and keep the generic name Translator for the interface? Specific implementations with a specific names, a generic interface with a generic name. That makes sense.

The burden of a descriptive name then lies purely on the implementations. If we rename TranslatorInterface to Translator, our former Translator class needs a new name. People tend to solve this problem by calling it DefaultTranslator, even I am guilty of this. But again, what makes it so special that it's called Default? Don't be lazy and think hard about what it does and why it's different from other possible implementations.

And what if I can't imagine several possible implementations? What if I can only think of one valid way? Then just don't have an interface.

Eh, There's Another Implementation

And it's here! We need a second implementation. It happens all the time. There has never been a need to store translations in more than one proven way, e.g. in a database, but now there is a new requirement and we need to have more translators in the application.

This is also when you clearly realize what the specifics of the original single translator were. It was a database translator, no default.

What about it?

  1. Make the name Translator the interface
  2. Rename the original class to DatabaseTranslator and it will implement Translator
  3. And you create new classes GettextTranslator and maybe NeonTranslator

All these changes are very convenient and easy to do, especially if the application is built according to the principles of dependency injection. No need to change anything in the code, just change Translator to DatabaseTranslator in the DI container configuration. That's great!

However, a diametrically different situation would arise if we insisted on prefixing/suffixing. We would have to rename the types from Translator to TranslatorInterface in the code across the application. Such renaming would be purely for the sake of convention, but would go against the spirit of OOP, as we have just shown. 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 the interface, we would rename again. Such an action may not be trivial at all, for example when the code is spread over multiple packages or used by third parties.

But Everybody Does It

Not everyone does. It's true that in the PHP world, the Zend Framework, followed by Symfony, the big players, popularized the distinction interface and abstract class names. This approach has been adopted by PSR, which ironically publishes only interfaces, yet includes the word interface in the name of each.

On the other hand, another major framework, Laravel, does not distinguish interfaces and abstract classes. Even the popular Doctrine database layer doesn't do this, for example. And neither does the standard library in PHP (so we have the Throwable or Iterator interfaces, the FilterIterator abstract class, etc.).

If we look at the world outside of PHP, C# uses the I prefix for interfaces, whereas in Java or TypeScript the names are not different.

So not everyone does it, but even if they did, it doesn't mean it's a good thing. It's not wise to mindlessly adopt what others are doing, because you may be adopting mistakes as well. Mistakes that the other would rather get rid of, it's just too big a bite nowadays.

I Don't Know in the Code What the Interface Is

Many programmers will argue that prefixes/suffixes are useful for them because they allow them to know what interfaces are right away in the code. They feel that they would miss such a distinction. So let's see, can you tell what is a class and what is an interface in these examples?

$o = new X;

class X extends X implements Y
{}

interface Y
{}

X::fn();

X::$v;

X is always a class, Y is an interface, it's unambiguous even without prefixes/postfixes. Of course, the IDE knows this too and will always give you the correct hint in a 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 won't know it. As we said at the very beginning, from a developer's point of view there should be no difference between what is a class and what is an interface. Which is what gives interfaces and abstract classes their sense.

If you were able to distinguish between a class and an interface here, it would deny the basic principle of OOP. And interfaces would become meaningless.

I'm Used to It

Changing habits just hurts 🙂 Often the idea of it hurts. Let's not blame people who are attracted to changes and look forward to them, for most of as the Czech proverb applies that habit is an iron shirt.

But just look at the past, how some habits have been swept away by time. Perhaps the most famous is the so-called Hungarian notation used since the eighties 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 echo $this->strName or $this->intCount++. Hungarian notation started to be abandoned in the nineties, and today Microsoft's guidelines directly discourage developers from using it.

It used to be an essential feature and today nobody misses it.

But why go to such a long time ago? You may remember that in PHP, it was customary to distinguish non-public members of classes with an underscore (sample from Zend Framework). This was back when there was PHP 5, which had public/protected/private visibility modifiers. But programmers did it out of habit. They were convinced that without underscores they would stop understanding the code. “How would I distinguish public from private properties in the code, huh?”

Nobody uses underscores today. And nobody misses them. Time has proven perfectly well that the fears were false.

Yet it's exactly the same as the objection, “How would I distinguish an interface from a class in 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 wants to go back either. As a friend said, “Try it and in a month you won't understand that 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, it's just that I already have my code built this way and changing it is very difficult. And if I start writing new code correctly without them, I'll end up with inconsistency, which is perhaps even worse than bad convention.”

In fact, your code is already inconsistent because you're using the PHP system library:

class Collection implements ArrayAccess, Countable, IteratorAggregate
{
	public function add(string|Stringable $item): void
	{
	}
}

And hand on heart, does it matter? Did you ever think that this would be more consistent?

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 big a role as it might seem. On the contrary, the eye prefers less visual noise, the brain prefers clarity of design. So adjusting the convention and start writing new interfaces correctly without prefixes and suffixes makes sense.

They can be purposely removed even from large projects. An example is the Nette Framework, which historically used I prefixes in interface names, which it started to phase out a few years ago, while maintaining full backward compatibility.