DEV Community

Cover image for Code Smell 135 - Interfaces With just One Realization
Maxi Contieri
Maxi Contieri

Posted on • Edited on • Originally published at maximilianocontieri.com

Code Smell 135 - Interfaces With just One Realization

Being generic and foreseeing the future is good.

TL;DR: Don't over-generalize

Problems

  • Speculative Design

  • Complexity

  • Over-Engineering

Solutions

  1. Remove the interface until you get more examples

Context

In the past, programmers told us to design for change.

Nowadays, We follow the scientific method.

Whenever we find a duplication we remove it.

Not before.

Sample Code

Wrong

public interface Vehicle {
    public void start();
    public void stop();
}

public class Car implements Vehicle {
    public void start() {
        System.out.println("Running...");
    }
    public void stop() {
        System.out.println("Stopping...");
    }
}

// No more vehicles??
Enter fullscreen mode Exit fullscreen mode

Right

public class Car {
    public void start() {
        System.out.println("Running...");
    }
    public void stop() {
        System.out.println("Stopping...");
    }
}

// Wait until more vehicles are discovered
Enter fullscreen mode Exit fullscreen mode

Detection

[X] Automatic

This is very easy for our linters since they can trace this error at compile time.

Exceptions

This rule applies to inter system definition and business logic.

Some frameworks define an Interface as protocol to be fulfilled.

On our bijections we need to model existing real world protocols.

Interfaces are the MAPPER correspondence to protocol.

Dependency injection protocols declare interfaces that are fulfilled with their realizations. Until then, they can be empty.

If your language defines an interface for test mocking, it is another code smell.

Tags

  • Over Design

More Info

Conclusion

We need to wait for abstractions and not be creative and speculative

Credits

Photo by Brian Kostiuk on Unsplash


I love software, because if you can imagine something, you can build it.

Ray Ozzie


This article is part of the CodeSmell Series.

Top comments (22)

Collapse
 
davidkroell profile image
David Kröll

I mostly agree with you opition, except a single thing: tests. I almost ever extract an interface for logic classes to write tests.

Collapse
 
mcsee profile image
Maxi Contieri

And how many realizations do you have ?

Collapse
 
davidkroell profile image
David Kröll

Typically just a single "real" one, the other ones are created by mocking frameworks (like NSubstitue for .NET)

Collapse
 
mcsee profile image
Maxi Contieri

I don't follow your tests example

Can you add a small piece of code as an example?

Collapse
 
davidkroell profile image
David Kröll
public class Car
{
    private readonly IEngine _engine;
    private bool isRunning;

    public Car(IEngine engine)
    {
        _engine = engine;
    }

    public void Start()
    {
        if (!isRunning)
        {
            _engine.Start();
            isRunning = true;
        }
    }
}

public interface IEngine
{
    void Start();
}

public class DieselEngine : IEngine
{
    public void Start()
    {
        Console.WriteLine("Starting diesel engine");
    }
}
Enter fullscreen mode Exit fullscreen mode

Assume you have a class Car which depends on a class DieselEngine. It would not be possible to write a unit test just for the Car when the concrete type DieselEngine is required from the Car constructor. Instead, the test have then to be written keeping in mind that also the concrete implementation for DieselEngine is going to be tested.

When you extract an interface IEngine, you can write a test for the Car itself again (without relying on concrete DieselEngine). Typically you do not have to create another realization of the IEngine interface - instead you can use a mocking framework which does this for you, and check if the method IEngine.Start is only called if the engine is currently off.

I hope this answers your question

Thread Thread
 
mcsee profile image
Maxi Contieri

Thank you very much for the code.

I think the problem is here

"Assume you have a class Car which depends on a class DieselEngine. It would not be possible to write a unit test just for the Car when the concrete type DieselEngine is required from the Car constructor."

Why not test the Car with a DieselEngine ? This is a real business scenario

Also, IMHO , Callling an interface IEngine is another smell. Even dough I know it is a standard practice.
Can I use your code snippet in another smell ?

Thread Thread
 
gorynych profile image
Stepan Mozyra

"Why not test the Car with a DieselEngine ?" - This is idea behind unit tests :) to have a single unit under the test.

If you do unit testing in your project - you will always have two implementations of your interfaces.

"This is a real business scenario" - Testing of business scenarios is just an another kind of tests.

Thread Thread
 
mcsee profile image
Maxi Contieri

Right !

You have two interfaces realizations. Therefore this code smell does not apply anymore

Thread Thread
 
davidkroell profile image
David Kröll

Can I use your code snippet in another smell ?

Yes feel free.

It depends on what you call a unit I think, bit you're right. If the DieselEngine is testable at all (could be a real DieselEngine which is not availabe during CI test run, etc).

Thread Thread
 
mcsee profile image
Maxi Contieri

Build your abstractions with TDD so they will be testaable from their inception

Collapse
 
jessica_veit profile image
Jessica Veit

Additionally - Dependency Injection in general. If you do add a second realization later, you have the first realizations reference everywhere (testability) and must rewrite everything.

Collapse
 
mcsee profile image
Maxi Contieri

Exactly. That is a valid counterexample. I'll add it

Thread Thread
 
mcsee profile image
Maxi Contieri

Done!

Thread Thread
 
jessica_veit profile image
Jessica Veit

Thank you for being that open! 😁

Collapse
 
explorer14 profile image
Aman Agrawal

Testing needs often drive the decisions to put an implementation behind an interface. Main reason being the ability to isolate the system under test from its runtime dependencies by creating appropriate test doubles that follow the same interface. Anymore coupling, and we are talking about integrated tests which though valuable can also be expensive to maintain in the long run as the system scales and can also get flaky over time. Any valuable system with reasonably high essential complexity better be designed with a modular/component architecture and then it's only right that these components be testable in isolation from their runtime dependencies. The way to achieve that then is putting abstractions that you can create test doubles out of.

If a component is testable with its runtime dependencies without increasing the testing effort or cost significantly, then I'd forego interfaces in those narrow cases and test with the real dependencies for e.g. classes that mostly have algorithmic logic executed in memory with other algorithmic dependencies that don't do I/O or interact with external systems.

Reusable libraries is another place where interfaces are very useful as extensibility hooks for consumers to provide their own implementations for.

Indiscriminately creating an interface for every class in your code without critically thinking, is wrong! There I agree with you!

Collapse
 
mcsee profile image
Maxi Contieri

I agree with you testability is one of the most important quality attributes of a system.
Test doubles decrease the confidence of a system since you are not testing a real one.
If you need to isolate your system you can build an interface and have two implementations: one for real and one for testing. Therefore you don't have just one realization. You have two. And you are no longer in code smell scenario

That said. Your last paragraph is the most important

Collapse
 
explorer14 profile image
Aman Agrawal • Edited

How is creating an implementation for testing, not a test double?

Thread Thread
 
mcsee profile image
Maxi Contieri

You need to use real objects for testing, not mock or test objects

Thread Thread
 
explorer14 profile image
Aman Agrawal

Interfaces for external dependencies allow you the flexibility of using either a test double or a real implementation without much overhead. Arguing about that is a moot point.

There is a lot to be said about levels and granularity of testing but that's not this post!

Thread Thread
 
mcsee profile image
Maxi Contieri

Right! there also a lot of test smells, many on this series and many others to come!

Collapse
 
adamkatora profile image
Adam Katora

This reminds me a lot of the Don Knuth quote, "Premature optimization is the root of all evil"

Collapse
 
mcsee profile image
Maxi Contieri

actually, this is my favorite quote