Being generic and foreseeing the future is good.
TL;DR: Don't over-generalize
Problems
Speculative Design
Complexity
Over-Engineeri...
For further actions, you may consider blocking this person and/or reporting abuse
I mostly agree with you opition, except a single thing: tests. I almost ever extract an interface for logic classes to write tests.
And how many realizations do you have ?
Typically just a single "real" one, the other ones are created by mocking frameworks (like NSubstitue for .NET)
I don't follow your tests example
Can you add a small piece of code as an example?
Assume you have a class
Car
which depends on a classDieselEngine
. It would not be possible to write a unit test just for theCar
when the concrete typeDieselEngine
is required from theCar
constructor. Instead, the test have then to be written keeping in mind that also the concrete implementation forDieselEngine
is going to be tested.When you extract an interface
IEngine
, you can write a test for theCar
itself again (without relying on concreteDieselEngine
). Typically you do not have to create another realization of theIEngine
interface - instead you can use a mocking framework which does this for you, and check if the methodIEngine.Start
is only called if the engine is currently off.I hope this answers your question
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 ?
"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.
Right !
You have two interfaces realizations. Therefore this code smell does not apply anymore
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).
Build your abstractions with TDD so they will be testaable from their inception
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.
Exactly. That is a valid counterexample. I'll add it
Done!
Thank you for being that open! 😁
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!
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
How is creating an implementation for testing, not a test double?
You need to use real objects for testing, not mock or test objects
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!
Right! there also a lot of test smells, many on this series and many others to come!
This reminds me a lot of the Don Knuth quote, "Premature optimization is the root of all evil"
actually, this is my favorite quote