Don't call your interfaces interfaces
Published
An old, prolific convention of software development has been to prefix or suffix interfaces with something that specifically designates them as such. So if I have an interface for a clock, rather than calling it Clock
, many would call it IClock
or ClockInterface
. In PHP, the convention is so ingrained that our core inter-framework operable PSR standards adopt this.
I am going to spend this article arguing against this convention.
If you need -Interface
, you probably have your abstractions wrong
Typically, then someone uses Interface
as a suffix, it is because they have a concrete implementation of the class without the suffix (which is in some sense the “real”, canonical, implementation) and they’re trying to stub the concrete functionality out when it comes to testing.
So to give an example, you might have Http\ClientInterface
and some concrete implementations Http\Client
(in production) and Http\TestClient
(for writing unit tests).
namespace Http;
interface ClientInterface
{
public function send(Request $request): Response;
}
// ...
class Client implements ClientInterface
{
public function send(Request $request): Response
{
// ...?
}
}
class TestClient implements ClientInterface
{
public function send(Request $request): Response
{
// ...?
}
}
Making the code more testable by using an interface is a good thing, and I support that. What concerns me is that simply by naming the interface obscurely, we lost some of the power that the abstraction brought us.
The core problem is this: is it clear how either Client
or TestClient
should behave based on their names? What implementation details is the original interface meant to be hiding here?
ClientInterface
’s core abstraction is that it lets you issue HTTP requests and receive HTTP responses programatically, using PHP classes as the models. The detail that you are hiding is how the client internally makes these requests (e.g. the vendor or library, network configuration, proxying, adding on additional headers, etc…).
There are manys I could implement the client. I could use PHP’s built in cURL library functions, for instance. I could build a wrapper around wget. I could use Guzzle by making a very slim adapter that fits the same interface. It would make sense for each of these implementations to be called:
CurlClient
WgetClient
GuzzleClient
respectively. None of them have a specific claim to be the canonical HTTP\Client
. Furthermore, I can create concrete implementations for my test scenarios with more informative names, such as:
ReturnSameResponseClient
ReturnNextResponseInSequenceClient
AlwaysThrowsExceptionClient
It takes little imagination to see how those clients would be used for unit tests. By avoiding the -Interface
suffix, we avoid being seduced by the deceptively simple naming of Client
and TestClient
(which as illustrated above, fail to be expressive in communicating intention compared to the above names).
Note that the ClientInterface -> Client
abstraction would be equally bad if you used Client
as the interface, but the had the implementations named ClientImpl
and ClientTest
. Kevlin Henney explains this – and the above point on naming your interfaces expressively – in his Refactoring to Immutability talk. It’s definitely worth a watch (and was one of the big inspirations for this article).
If you don’t need -Interface
, it adds no extra value as a suffix
Using the Http\Client
example above, I already took issue with the extra noise that this created when reading the code. To declare ClientInterface
as an interface at all, it already needs the interface
keyword. And only interfaces
can ever be implemented
, so it seems redundant to read implements ClientInterface
. So from an readability standpoint, it makes the code longer without communicating extra context.
Some will argue that a positive tradeoff for keeping the suffix is that it makes it easier to find interfaces when searching for them via a file browser or locating them in an IDE. Modern IDEs tend to be feature-rich enough to highlight different class types though: they allow you to distinguish concrete classes from interface
, abstract
and even final
classes at a glance through their icons. Some will even let you search specifically by class type. So I don’t see this as much of a benefit.
Furthermore, if I am searching for the interface outside of an IDE setting (e.g. searching for files on Github), I’m not going to be searching via the interface name - I’ll be searching by the behaviour of the interface in question. And with more descriptive naming of the concrete implementations, it becomes easy to single out the interface at a glance. If you saw these file names:
src/Storage/MySqlArticleRepository.php
src/Domain/ArticleRepository.php
test/Storage/ArrayArticleRepository.php
test/Storage/AlwaysThrowsExceptionArticleRepository.php
How quickly can you suss out which one is the interface?
What of abstract classes?
Another commonly given reason is that it is useful to know in the context of the function/method that is calling the interface that it is actually an interface. So:
use Http\ClientInterface;
function doSomething(ClientInterface $client): void
{
// ...
}
In the context of doSomething()
, you know that you’re dealing with an interface and can’t rely on any concrete assumptions about the implementation, just based on the name.
This is true, but it also defeats the purpose of the abstraction. I shouldn’t need to care what the implementation is in the first place - I shouldn’t even need to know that it is an interface
! I should only be using the publicly exposed methods that form part of the contract of $client
, not any hidden implementation details of it. This is true regardless of whether I type-hint $client
with a concrete class
or an interface
.
Suppose that I wanted to make ClientInterface
into an abstract class
instead. The naming convention would force me to rename this entity to AbstractClient
:
namespace Http;
abstract class AbstractClient
{
public function send(Request $request): Response;
}
doSomething()
now needs its method signature changed, despite the fact that the contract of $client
is exactly the same as before and the implementation details are still equally as hidden.
use Http\AbstractClient;
function doSomething(AbstractClient $client): void
{
// ...
}
If you’re dealing with just your own codebase, then renaming this won’t be difficult with modern IDEs. But if you’re designing a public library with interfaces that can be type-hinted by consumers, then you have one of three options:
Rename the client interface to
AbstractClient
and introduce a breaking change for consumers of your library. They all need to rename this usage in their codebases when they next update, despite the fact that it will give them no tangible benefits.Introduce
AbstractClient
as the new cannonical client “interface” and deprecateClientInterface
until it is eventually phased out, ala (1). To be fully interoperable, this will also requireAbstractClient
to implementClientInterface
:/** * @deprecated * @see AbstractClient */ interface ClientInterface { public function send(Request $request): Response; } abstract class AbstractClient implements ClientInterface // ...and is even more verbose! { abstract public function send(Request $request): Response; }
Introduce the client as an abstract class, but keep it named
ClientInterface
and hope that it doesn’t confuse anybody 🤞
None of these are good options at all, and would all be avoided if the interface/abstract class were just called Client
.
Rightfully you might ask why you would ever go from interface
to abstract
class or vice-versa. If they are functionally equivalent for your use case(s), then you should just pick one and stick with it, right?
In the case of [Abstract]Client
, I may choose to make the send()
method concrete and do some work before/after processing in order to minimize code duplication across the different implementations. If every implementation had to do the same pre-processing of the requests and responses, I could do this:
abstract class Client
{
// this is implemented by subclasses instead of send()
protected function fetch(Request $request): Response;
public function send(Request $request): Response
{
$preprocessed = $this->preprocessRequest($request);
$response = $this->fetch($preprocess);
return $this->preprocessResponse($response);
}
private function preprocessRequest(Request $request): Request
{
// ...
}
private function preprocessResponse(Response $response): Response
{
// ...
}
}
If the class is just named Client
, then the consumers of the library would not need to make any changes to their method signatures. Now, if they have their own concrete implementations of the interface then it would indeed be problematic (i.e. you’d need to do (1) from above), but the consumers of your library are far more likely to be using the interface for type-hinting, and will be depending on your concrete implementations, rather than creating their own userland implementations. This doesn’t mean that there aren’t valid use cases for this though - a plugin architecture that allows users to augment an application’s functionality by satisfying a small interface would be a very good one. But in this scenario, you would be making a very calulated and deliberate decision to expose the interface to users; such a decision would probably come at the cost of guaranteeing or promising some level of stability in the interface. Users are less likely to build plugins for your system if they know they’ll have to re-write them every few iterations.
As an aside: using an abstract class for Client
isn’t the only – or even the recommended – way of achieving pre/post processing on a function’s arguments and return values. There are other design patterns out there like the decorator pattern which can do the job elegantly: it is up to you to decide what is best for the job given your project’s constraints.
Hopefully this gives some pause for thought when it comes to naming your objects and abstractions. In short:
- Where possible, avoid using a suffix or prefix to communicate that something is an interface.
- Use a name for the interface that communicates the behaviour underlying the concept. This is where the power of abstraction lies.
- Ensure that all implementations/subclasses have an expressive, informative enough name to avoid confusion.
This naming convention also applies in other cases for similar reasons:
- Abstract classes (as illustrated above)
- Traits
- Exceptions - only exceptions can ever be thrown or caught, and a good exception name is more informative than a suffix;
FileNotFound
,InvalidFileFormat
,ReadAccessDenied
) - Events - if you have a base
Event
class (or interface), then expressive (past-tense) event names will make a suffix redundant as it would with exceptions (e.g.LoanApplicationApproved
,CurrentAccountClosed
,ItemAddedToBasket
)
I am open to discussion on this though: if you’ve had scenarios that seemed impossible to resolve without having these kinds of naming conventions, or you’ve also seen fit to abandon the convention, please leave a comment below!