- So I can smell interface pollution
almost from a mile away and normally
interface pollution is the result of starting
with interfaces instead of your concrete implementation
and up til now I've been trying to show you
how powerful it is to start with
a concrete implementation and refactor
into the decoupling again we're focusing on data
all the way through.
But I wanna show you a piece of code
that maybe can help you understand
a little bit more about this interface pollution
where it sometimes come from.
I had a friend who had implemented a piece of code.
It was supposed to implement boilerplate code
for a tcp server and he had asked me to do
a small code review and as soon as I opened up
the source code file with him,
I saw this interface at the top of the source code file.
Server interface start stop and wait.
Immediately this interface smelled very very bad for me.
Remember I told you an interface should describe
behavior right?
And server's not behavior.
Server is a thing.
It's a server.
It's something real, right?
And so that immediately is a smell.
Another smell is that it's got these three methods in it
and when I start thinking about what a tcp server is,
start stop and wait, it seems like to me on the surface
this is already modeling an entire API set.
I can't imagine everything in the code
needs all three behaviors all the time.
But the first question I really ask him is,
"Dude are you asking the user to implement this behavior?"
And his answer is no, I implemented this behavior.
I go 'I thought so.'
I go 'Do you have multiple implementations
of a tcp server in your code base?'
And he said, 'No, I got one implementation.'
There's another smell.
I don't see anything that needs to be decoupled.
I'm not asking the user for implementation details,
I don't have multiple implementation.
So I asked him why is this interface here?
It doesn't seem reasonable or practical.
And he said the one thing you don't wanna say to me.
'Bill, the interface is there because we have
to use interfaces.'
No.
You do not have to use interfaces.
You have to make engineering choices.
Remember interfaces are the cost of
indirection and allocation and so this interface
is serving absolutely no purpose without even me
looking at the rest of the code,
I already know that there's pollution here.
When you look at the rest of the code
it just really comes home.
There's our concrete implementation implemented
as an unexported type and then the factory function
is returning the value but as the interface.
Again this is no, no, no, no, no.
Factory functions initialize concrete types
should return the concrete type value
and the caller will deal with the decoupling if it's needed.
All of this stuff is a very, very, very bad smell.
And the interface is implementing or defining
a method set for the entire API.
Look, a caller, a user's gonna do this.
New server and then use the API.
And if I switch out the interface for the concrete type,
this API, this code doesn't change.
If anything, this code actually gets better
because we have potential of reducing allocations.
Major, major pollution here.
Took me about a half an hour to talk my friend down
to get rid of the interface.
But there's some really smells here
that I just wanna reiterate as we looked at this code here.
Okay?
I saw an interface that matches the entire API
of the concrete type.
No, no, no, no.
Interfaces define behavior.
We should only have those behaviors we need
to decouple those aspects of the API
that need to be decoupled.
Also we've got an interface that's exported,
we have a concrete type that's unexported
and yet we're returning that unexported type
back to the user even though we're doing it
through the interface that's a big, big smell.
That factory function should be returning
concrete values or pointers, not the interface.
Okay?
And if I remove the interface, nothing changes from the API
because the interface really wasn't decoupling anything.
So what I really wanna do with this code
and what I talked to my friend about was
get rid of the interface.
It is pure pollution.
Use a concrete type exported type,
implement your factory function and return the
pointer to the exported value, implement your API
and let the user work in the concrete.
There are not multiple implementation of this
and the user has to provide you nothing.
Here's some guidelines that we're gonna be
maintaining as we go forward.
We wanna use an interface when the user of the API
needs to provide some implementation detail
that we're not able to implement ourselves.
Or if we have multiple implementations of something
we wanna layer of decoupling in our code
around that concrete data.
Or if we've identified parts of our API or code
that requires decoupling because of change.
And always question an interface when its purpose
is for writing testable APIs.
I cannot say this enough.
If I hear somebody say, 'The interface is there
because I have to be able to mock in order
to be able to test,'
I'm gonna scream.
Mocking isn't always really the right answer.
If you're mocking a database, you are now wasting time.
Those tests are useless because mocking a database
isn't going to really give you accuracy whether
that code works and with technology like Docker today
you can literally spin up that database
in a docker container when you type go test
and validate that your database stuff really works.
Not mocking.
There's lots of things I see people mocking
when it doesn't need to be mocked
where either we can leverage data for the testing
side of it or we can leverage docker
to put those systems in place.
I'm not saying that you shouldn't be mocking things
all the time, right.
We need to make smart engineering decisions.
There are times where maybe a third party API
could absolutely be mocked.
We have expected behavior from the API
and I don't have to necessarily go out and hit it.
But databases, sometimes other types of systems,
mocking is a waste of time.
So let's not just say that I need an interface
because I have to write tests.
Our APIs are for users, you have to make sure
that they're layered properly, that they're testable,
but we don't wanna do things to an API for the tests
that are gonna make the API less usable
for the person who has to use it, okay.
So if we're not providing support for decoupling,
if it's not clear how the interface is making
the code better, probably isn't, we should probably
think about removing that interface
and sticking with the concrete types.