Prefixes and Suffixes Do Not Belong in Interface Names

2 years ago by David Grudl  

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?

  1. Make the name Translator the interface
  2. Rename the original class to DatabaseTranslator and have it implement Translator
  3. Create new classes GettextTranslator and maybe NeonTranslator

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

  1. 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.

    2 years ago
  2. The example with renaming types across the application from Translator to TranslatorInterface got me. I am converted now.

    2 years ago

Sign in to submit a comment