17

Given a constructor that will never, ever, have to use any different implementations of several objects that it initializes, is it still practical to use DI? After all, we might still want to unit test.

The class in question initializes a few other classes in its constructor and the classes it uses are pretty specific. It will never use another implementation. Are we justified in avoiding trying to program to an interface?

Seph
  • 279
  • 2
  • 9
  • possible duplicate of [Design for future changes or solve the problem at hand](http://programmers.stackexchange.com/questions/59810/design-for-future-changes-or-solve-the-problem-at-hand) – gnat Sep 23 '13 at 11:09
  • 3
    **KISS** - is always the best approach. – Reactgular Sep 23 '13 at 13:12
  • 5
    In my opinion this question is more specific than the proposed duplicate. [design-for-future-changes-or-solve-the-problem-at-hand](http://programmers.stackexchange.com/questions/59810/design-for-future-changes-or-solve-the-problem-at-hand), Therefore i vote to **not close this** – k3b Sep 23 '13 at 14:58
  • 1
    Isn't testability enough of a reason? – ConditionRacer Sep 23 '13 at 15:53
  • Doesn't really sound like a duplicate so much as answering itself. If it will never, ever happen, designing for it isn't designing for future changes. Even architecture astronauts only design for changes that will never happen out of bad judgement. –  Sep 23 '13 at 16:12
  • @k3b all of the answers posted so far (including your one) are based on the reasoning that perfectly matches [suggested duplicate](http://programmers.stackexchange.com/questions/59810/design-for-future-changes-or-solve-the-problem-at-hand), all refer to, speculate about and evaluate possible future changes. There's nothing specific in there compared to duplicate question – gnat Sep 23 '13 at 17:03
  • IMHO "Never, ever" and other hard claims loose strength when used in software developement context. At best you could aim at "shouldn't, to our current knowledge and requirements". – rszalski Sep 29 '13 at 16:17

8 Answers8

15

It depends on how certain you are that "never, ever" is correct (during the time your app will be used).

In general:

  • Don't modify your design just for testability. Unit tests are there to serve you, not vice versa.
  • Don't repeat yourself. Making an interface that will only ever have one inheritor is useless.
  • If a class isn't testable, it's likely not flexible/extensible for real use cases either. Sometimes that's fine - you're unlikely to need that flexibility, but simplicity/reliability/maintainability/performance/whatever is more important.
  • If you don't know, code to the interface. Requirements change.
Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • 12
    Almost gave you -1 for "don't modify your design *just* for testability". To gain testability is IMHO one of the tops reasons for changing a design! But your other points are ok. – Doc Brown Sep 23 '13 at 12:28
  • 6
    @docBrown - (and others) I do differ here from many, and perhaps even the majority. ~95% of the time, making things testable also makes them flexible or extensible. You should have that in your design (or add that to your design) _most_ of the time - testability be damned. The other ~5% either has odd requirements that prevents this sort of flexibility in favor of something else, or is so essential/unchanging that you'll find out pretty quickly if it's broken, even without dedicated tests. – Telastyn Sep 23 '13 at 12:51
  • 4
    may be sure, but your first statement above could be misinterpreted too easily as "leave badly designed code as it is when your only concern is testability". – Doc Brown Sep 23 '13 at 12:56
  • 1
    Why would you say "Making an interface that will only ever have one inheritor is useless."? Interface can work as a contract. – kaptan Sep 23 '13 at 18:59
  • 2
    @kaptan - because the concrete object can work just as well as a contract. Making two just means you need to _write the code twice_ (and modify the code twice when it changes). Granted, the vast majority of interfaces will have multiple (non-test)implementations or at you need to prepare for that eventuality. But for that rare case where all you need is a simple sealed object, just use it directly. – Telastyn Sep 23 '13 at 19:06
  • 1
    I don't see any problem adding an extra constructor to make something testable, please DO modify your design for testability if you want your code to be tested. – Chris Lee Sep 24 '13 at 21:14
  • 1
    @chrislee - if your code isn't testable, it's probably not flexible or extensible or usable or... Fix your design for those other reasons. If the design is otherwise good, adding testability will likely trade-off against those other things that more directly impact your customer. – Telastyn Sep 25 '13 at 00:17
  • @Telastyn may be you can something more to explain your position on the first point that you made by taking the gist in your follow-up comments to Doc Brown as a starting point. I feel that that will add more value to your answer and ultimately result in a lot more up votes than it garnered now. – Geek Sep 27 '13 at 14:06
  • I agree with kaptan: it's happened to me more than once that I thought there shouldn't be an interface with only one inheritor, so I didn't write one. Then, 6 months down the line, I have references to that concrete class all over the app and switching to an interface is impossible--so even though it would be really nice to have unrelated implementations, it's not possible without completely gutting large parts of the code. – Amy Blankenship Sep 27 '13 at 22:14
  • 1
    Every interface has AT LEAST TWO inheritors: its implementation and a test mock. – Facio Ratio Nov 06 '13 at 01:45
12

Are we justified in avoiding trying to program to an interface?

While the advantage of coding against an interface is pretty evident, you should ask yourself what exactly is gained by not doing it.

The next question is: is part of the responsibility of the class in question to choose the implementations or not? That may or may not be the case and you should act accordingly.

Ultimately, there's always potential for some goofy constraint to come out of the blue. You may want to run the same piece of code concurrently, and the possibility of injecting the implementation might help with synchronization. Or after profiling your app you may discover you want an allocation strategy different from plain instantiation. Or cross-cutting concerns come up and you don't have AOP support at hand. Who knows?

YAGNI suggest that when writing code, it's important not to add anything superfluous. One of the things that programmers tend to add to their code without even being aware of it, are superfluous assumptions. Like "this method might come in handy" or "this'll never change". Both add clutter to your design.

back2dos
  • 29,980
  • 3
  • 73
  • 114
  • 2
    +1 for citing YAGNI here as an argument *for* DI, not against it. – Doc Brown Sep 23 '13 at 12:35
  • 4
    @DocBrown: Considering that YAGNI can be used here, to also justify *not* using DI. I think that's more of an argument against YAGNI being a useful measure for anything. – Steven Evers Sep 23 '13 at 15:29
  • 1
    +1 for superfluous assumptions being included in YAGNI, which have hit us in the head a few times – Izkata Sep 23 '13 at 15:29
  • @SteveEvers: I think using YAGNI to justify ignoring the dependency inversion principle precludes a lack of understanding of YAGNI or DIP or maybe even some of the more fundamental principles of programming and reasoning. You should only ignore the DIP if you *need* to - a circumstance in which YAGNI is simply not applicable by its very nature. – back2dos Sep 24 '13 at 10:50
  • @back2dos: The supposition that DI is useful because of 'maybe-requirements' and "who knows?" is precisely the train of thought that YAGNI is intended to combat, instead you're using it as a justification for additional tooling and infrastructure. – Steven Evers Sep 24 '13 at 17:17
  • @back2dos: Also, please don't conflate DI with the DIP and infer the former sits alongside fundamental programming principles. That's disingenuous. – Steven Evers Sep 24 '13 at 17:18
  • @SteveEvers: Yesterday YAGNI wasn't useful, today it's a tool intended for combat against certain trains of thought. Just what is your point? Also, you're putting words in my mouth. Where exactly am I suggesting additional tooling or infrastructure? I am suggesting not to code against an implementation unless there is a gain. And I am suggesting to stick to the SRP. Then I'm trying to illustrate that "never, ever" is a bit optimistic when it comes to requirements. And then I am saying you should use YAGNI. Don't implement stuff you wont need and don't make assumptions that are superfluous. – back2dos Sep 24 '13 at 17:53
  • @back2dos I do not follow your point when you say "You may want to run the same piece of code concurrently, and the possibility of injecting the implementation might help with synchronization.". Can you please explain a little bit? – Geek Sep 27 '13 at 13:58
  • @Geek: Without any context this is really rather theoretical. But assume that our class `Owner` has a `Writer` to append output to files. Now if we start parallelizing things, this may cause issues when multiple owners want to append payload to the same file at once. If the `Writer` is injected from outside, we can resolve this issue by injecting an implementation that will make sure this keeps working as expected (say by buffering and then atomically flushing when the writer is closed). – back2dos Sep 27 '13 at 17:56
  • MPO is that once you get to a certain level of experience, if you think you'll probably need it, you probably need it and you will cost yourself more time by not building it. Prior to that level of experience, you should probably follow best practices exclusive of YAGNI, because the only way to develop that instinct is to build things to the best of your capacity for many years. – Amy Blankenship Sep 27 '13 at 22:18
7

It depends on various parameters but here are a few reasons why you could still want to use DI:

  • testing is quite a good reason in itself I believe
  • the classes required by your object might be used in other places - if you inject them it enables you to pool the resources (for example if the classes in question are threads or database connections - you don't want each of your classes to create new connections)
  • if initialising those other objects can fail, code higher up the stack will probably be better at dealing with problems than your class which will then probably simply forward the errors

Bottom line: you can probably live without DI in that case but there are chances that using DI will end up in cleaner code.

assylias
  • 1,187
  • 8
  • 20
3

When you design a program or a feature, part of the thought process should be how you are going to test it. Dependency Injection is one of the testing tools, and should be considered as part of the mix.

The additional benefits of Dependency Injection and using interfaces instead of classes are also beneficial when an application is extended, but they can also be retrofitted to the source code later quite easily and safely using search and replace. - even on very large projects.

Michael Shaw
  • 9,915
  • 1
  • 23
  • 36
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Many answers to this question can be debated as "you might need it because .." as YAGNI if you do not want to do unittesting.

If you are asking what reasons beside unittests require to program to an interface

Yes, Dependency Injection worth it outside of UnitTesting if you need inversion of control. For example if one module implementation need an other module that is not accessable in its layer.

Example: if you have the modules gui => businesslayer => driver => common.

In this scenario businesslayer can use driver but driver can not use businesslayer.

If driver needs some higherlevel functionality you can implement this functionality in businesslayer which implements an interface in common layer. driver only needs to know the interface in common layer.

k3b
  • 7,488
  • 1
  • 18
  • 31
1

Yes you are - firstly, forget about unit testing as a reason to design your code around the unit test tools, it is never a good idea to bend your code design to fit an artificial constraint. If your tools force you to do this, get better tools (eg Microsoft Fakes/Moles that allow you many more options for creating mock objects).

For example, would you split your classes up into only-public methods just because the test tools do not work with private methods? (I know the prevailing wisdom is to pretend you don't need to test private methods, but I feel this is a reaction to the difficulty in doing so with current tools, not a genuine reaction to not needing to test privates).,

All in all it comes down to what kind of TDDer you are - the "mockist" as Fowler describes them, need to change the code to suit the tools they use, whereas the "classical" testers create tests that are more integration in nature (ie test the class as a unit, not each method) so there is less need for interfaces, especially if you use the tools that can mock concrete classes.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
  • 3
    I don't think the prevailing wisdom that private methods shouldn't be tested has anything to do with the difficulty of testing them. If there were a bug in a private method, but it didn't affect the object's behavior, then it doesn't affect anything. If there were a bug in a private method that did affect the object's behavior, then there's a failing or missing test on at least one of the public methods of an object. – Michael Shaw Sep 23 '13 at 18:37
  • That comes down to whether you test for behaviour or state. The private methods need to be tested in some way, obviously, but if you're a classical TDD person, you'll test them by testing the class "as a whole", the test tools most people use are focussed on testing each method individually... and my point is the latter are effectively not testing properly as they miss the chance to perform a full test. So I agree with you, while trying to highlight how TDD has been subverted by how some (most?) people do unit testing today. – gbjbaanb Sep 24 '13 at 08:17
1

Dependency Injection also makes it clearer that your object HAS dependencies.

When someone else goes to use your object naively (without looking at the constructor), they will find that they will need to set up services, connections, etc. That were not obvious because they didn't have to pass them in to the constructor. Passing in the dependencies makes it clearer what is needed.

You are also potentially hiding the fact that you class may be violating SRP. If you are passing in many dependencies (more than 3), your class may be doing too much and should be refactored. But if you are creating them in the constructor, this code smell will be hidden.

Schleis
  • 3,376
  • 1
  • 19
  • 21
0

I agree with both camps here and the matter is still open for debate in my opinion. YAGNI demands that I not tackle changing my code to fit supposition. Unit testing is not a problem here because I firmly believe that the dependency will never, ever change. But if I was Unit testing as intended to begin with, I would have never reached this point anyway. As I would have slimed my way into DI eventually.

Let me offer another alternative for my scenario specifically

With a factory pattern I can localize dependencies in one place. When testing I can change implementation based on context, and that's good enough. This doesn't go against YAGNI because of the benefits of abstracting several class constructions.

Seph
  • 279
  • 2
  • 9