15

During a code review I have started having a bit of a dilemma as to whether use dependency injection or not. I would like to hear your thoughts, because this is kind of an ongoing theme and would help in the future code reviews as well.

Before I start I want to say that from my knowledge DI is mostly used for dependency management and better, much easier unit testing (correct me if I am wrong please).

Now here is the scenario:

This is the class that will be used as a dependency only once and probably for good. Meaning it will stay that way for a while and no other class will be using it.

Reason being is because it is a refactoring of some legacy code and there was not much to be done but to separate it into another class, at least as a first good step, for following the SRP.

Also, another thing is that the hypothetical doSomethingImportant() hits the database.

public SomeClass
{
   public Object doSomethingImportant()
   {
      ...
   }
}

Based on that information do you think it is okay to new up the dependency in the other class as opposed to using DI since:

Dependency management argument kind of falls off since it is only going to be used once.

&

Unit testing also falls off since I would rather do an integration or acceptance test to see how the method is interacting with the database in a real-life application.

public SomeOtherClass
{
   private readonly SomeClass _someClass = new SomeClass();

   public Object doSomethingImportantUsingDependency()
   {
      ...
   }
}

I was personally inclined towards doing DI because it is considered good practice, but for a second there it felt like I was blindingly following the rules and not thinking it over since there are always exceptions to the rule.

What are your thoughts on this? I would love to hear.

PS: I don't think this is a generic "when should I use DI" question because it is very specific to this particular situation when unit tests are of no use and the class is going to be used only once so there is no need to centralize it for dependency management (even though it is good practice in general).

AvetisCodes
  • 1,544
  • 3
  • 14
  • 26
  • 2
    wonder how it is _possible_ to use it only once? I mean, one gets at least one use in the main code and at least one more in unit test - that makes it two (and frankly, my experience has been that _second_ use - in tests - is often much more worthwhile than first one) – gnat Apr 08 '15 at 16:03
  • @gnat It is like a very specific helper class for the one it is being injected into. I guess you could argue as to why not put them all in one and make the method private - and that is a good point but it is frankly 2 different responsibilities which is the reason it was extracted. But currently only used once and will probably stay that way for a little while. As for testing - it would much rather be tested with integration/acceptance than unit which takes away the need for it being mocked. Hope that clarifies. – AvetisCodes Apr 08 '15 at 16:09
  • I see, thanks. Consider [edit]ing to help readers see that use in unit testing is not an option in your case (I may be not the only one who counts `main+test>=2`) – gnat Apr 08 '15 at 16:32
  • @gnat I have explicitly mentioned that in my writing here is the quote: "the unit testing also falls off since I would rather do an integration or acceptance test to see how the method is interacting with the database in a real-life application." – AvetisCodes Apr 08 '15 at 16:33
  • 1
    sorry, I did not notice that. Though, given that [one of the answers](http://programmers.stackexchange.com/a/278576/31260) already appears to be pointing to use in unit tests, you may consider making it somehow easier for readers to see – gnat Apr 08 '15 at 16:37
  • 1
    dependency injection need not be centralized at all (see other answers). And even though this class may be used only once, it still needs to be tested for its behavior. Plus, more importantly, the class using it needs to be tested. And though you could test both classes through the outer one (the one aggregating the other class), your test classes and cases will be much simpler if you focus them on a single class. Which means that the outer class needs to be tested only for its own behavior and its interaction with the inner class. And that automatically is an argument for some form of DI. – Marjan Venema Apr 08 '15 at 18:18
  • 1
    "Unit testing also falls off since I would rather do an integration or acceptance test to see how the method is interacting with the database in a real-life application.". integration or acceptance tests are more costly in terms of maintenance than a unit test, so I'm not sure why those would be your preference. If this is going to be common as you move forward with other classes, the costs of this are going to be very real. Especially if the class representing the legacy code will be rewritten to not be legacy (by further decomposition), you may want to rethink. – Andy Apr 09 '15 at 01:44
  • @Andy I totally disagree that acceptance and integration tests are more costly in terms of maintenance. Please explain yourself. – AvetisCodes Apr 09 '15 at 02:04
  • DI can be useful for mocking in tests, FWIW – 2rs2ts Apr 09 '15 at 02:28
  • from the perspective of long term maintenance unit tests have rather strong benefit in that they make [convenient indication when design becomes too complicated](http://programmers.stackexchange.com/a/184472/31260) and provide nearly instant feedback on whether your attempt to clean it up is successful or not. Compared to that, black box testing tends to offer much weaker protection against turning software into [Big Ball Of Mud](http://programmers.stackexchange.com/a/232413/31260) – gnat Apr 09 '15 at 10:53
  • @AvetisG Seriously? Go google it. Relying on external state for automated tests makes them that much more fragile, prone to false negatives and slower (DB will be much slower than in memory). Acceptance testing means you have people manually trying stuff, which besides the fact that it takes longer also means if you ever find the error, you're much further down the development pipeline than if you had a good unit test and the further away from a local developer workstation it takes to find / fix a bug the more expensive it is. – Andy Apr 09 '15 at 15:07
  • @Andy Acceptance tests are great for making sure the whole works as expected so there are cases when it is much more preferred than unit tests. In this case that I have currently it makes a lot more sense to write acceptance tests instead of unit tests because 1) It is an integration piece where there is a lot of dbs, services invovled mocking all that is just going to be nightmare 2) some of these legacy methods new up object that hit the database on their creation (crappy? totally agreed but it is that way for now at least). – AvetisCodes Apr 09 '15 at 15:14
  • @Andy Hope that makes sense, but overall I agree that too many acceptance tests could slow down your coninuous integration/build times - which is why I use acceptence and integration when needed like in this case. – AvetisCodes Apr 09 '15 at 15:15
  • @Andy my mistake was that I made it sound like I totally disagree with all of the cases which is not true and didn't intend to sound it that way. My intention was to disagree with it for this particular case. – AvetisCodes Apr 09 '15 at 15:16
  • @AvetisG no, it makes no sense whatsoever, because all those external pieces need to be kept in a state you expect or your tests will start failing, and you won't know if its the code or the state that's broken. They also will be far less detailed (offer less coverage) than good unit tests. I've done plenty of the kinds of tests your talking about, and its far easier to mock than try to keep web services / DBs in their proper state. – Andy Apr 09 '15 at 15:17
  • If by proper state you mean them not working correctly then that means the acceptance tests just exposed that you need to fix db/service or whatever else is in the mix, if by proper state you mean the data needs to be exactly that which the acceptance test needs to work then that is completely trivial - you just dedicate an entry in the database to the acceptance test. I don't see how that is much maintenance. As for web services I haven't had any big problems with keeping the state up. – AvetisCodes Apr 09 '15 at 15:35

4 Answers4

15

In C# it is trivial to provide optional dependency injection without coupling yourself to your dependency too tightly:

public class SomeOtherClass {
    private readonly ISomeClass _someClass;

    public SomeOtherClass(ISomeClass dependency = null) {
        _someClass = dependency ?? new SomeClass();
    }
}

(or you can make the constructors explicit if your company dislikes default params)

In my experience, "oh we'll never need another one" is naive. Business changes. Technology changes. Make dependencies flexible.

And this sort of thing strikes the right balance between usability (yes, you'll almost always use the common/default one) and flexibility (but the ctor is there if you need it) - all with a nice simple line of code, while also providing some semblance of error correcting robustness. It's so trivial and clearly beneficial, there's no reason not to do it for the simple/straight-forward case.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • 4
    Well... you *are* coupling yourself to the constructor of "SomeClass()" That's probably what the OP should be thinking about -- not so much the dependency itself, but the complexity of getting "SomeClass()" into a usable state. Why is DI good? Primarily because it decouples state management of classes. If you know for sure that "SomeClass()" is a simple class with minimal state and always will be, then using a default constructor isn't so troublesome. On the other hand, if you have to build 10 classes to create one instance of "SomeClass()" (or think you might some day)... – Calphool Apr 08 '15 at 15:57
  • @Calphool - if you need to build some more complex dependency, then you're right - it doesn't belong here and you should just have the ctor to pass that dependency in. – Telastyn Apr 08 '15 at 15:58
  • @Calphool A class's constructor is part of its public API. Knowing how to construct `SomeClass` is no more harmful than knowing about any of its methods that don't appear in `ISomeClass`. The same applies transitively to any classes required to create `SomeClass`. – Doval Apr 08 '15 at 16:58
  • 1
    @Doval: Think about how constructors are *different* from other methods, and you'll probably begin to see why this kind of coupling can become insidious over time (it's innocent in the beginning, and falls apart as a system grows, which is why comment was qualified). This is what DI frameworks like Ninject were invented to solve. – Calphool Apr 08 '15 at 17:33
  • @Telastyn thanks for your answer but why are you making the dependency optional? It is required. I was asking whether to new it up in the class or to DI it since all of the good reasons for DI, those being unit testing and dependency management fall off. I do agree though with what you said about keeping it flexible. Though in this case it is highly likely that it won't need to be changed for a long time - based on the business model/domain/upper management. – AvetisCodes Apr 08 '15 at 17:55
  • @Telastyn I also agree that since it is such an easy thing to accomplish why not do it and set yourself up for success but that was exactly my point, that being, are there exceptions for DI and maybe this is a case for it when even if it is simple to accomplish it is unnecessary. But I guess at that point it becomes a preference issue instead of good practices issue. – AvetisCodes Apr 08 '15 at 17:59
  • 2
    @Calphool The point of DI isn't to reduce the complexity of instantiating something. DI makes that someone else's problem, but not because creating the dependency is hard; it does so because creating the dependency couples you to it. But if you're going to provide a default implementation, you're going to be coupled to it no matter what. If creating the instance is complex, passing the buck to someone else doesn't solve the problem; the code to do so has to be written by someone, somewhere. – Doval Apr 08 '15 at 18:05
  • @AvetisG - if you are ***always*** using `SomeClass` why even ask? It's not a dependency at that point but a compositional element. Granted, I think that's a terrible idea. If it's independent enough to be its own class, it's independent enough to change independently in the future (leading to supplying a different dependency). – Telastyn Apr 08 '15 at 18:27
  • @Telastyn I totally agree with your point there but it is a bit of a hybrid of what you said. It IS an independent class yet from knowing the business I know it won't need to change anytime soon (This is all legacy stuff hence the weirdness of it all). This is why it was bothering me because doing DI makes sense but then you end up asking yourself if it is unneccesary. I know I can just say it "screw it I will just DI it", but this is more for me to know how to deal with this in the future and clarify the grey zone. – AvetisCodes Apr 08 '15 at 18:36
  • @AvetisG - "oh that won't ever need to change." is the "640K ought to be enough for anyone" of program design quotes. All of these design principles exist because things _invariably_ change - especially the ones we didn't think would. – Telastyn Apr 08 '15 at 18:51
  • @Telastyn I definitely hear ya on that one, which is the reason why I am more inclined towards going with DI. I just wanted some good reasons from other people to make sure it is not just my personal bias. Thank you for your answers! – AvetisCodes Apr 08 '15 at 18:53
  • @Doval: Your argument, taken to its logical conclusion is "Why use DI at all, coupling happens *somewhere*." It's an ancient argument that's been debated for at least 15 years. That's why these discussions tend to be pointless. Everyone is speaking a truth, but no one is describing the *context* in which the truth should be applied. That's what I was *trying* to do in my original comment. – Calphool Apr 09 '15 at 18:12
  • @Calphool Not sure how you reached that conclusion. I said that the purpose of DI is to break coupling between *a class* and *its dependency*. There's no way to go from that to "Why use DI at all, coupling happens *somewhere*" since I never said DI tries to remove coupling everywhere (which is impossible). – Doval Apr 09 '15 at 18:40
  • @Doval: Are we talking about a DIF, or just DI in the abstract here? I would agree with you if we're ignoring DIFs. – Calphool Apr 09 '15 at 18:48
11

There's a development principle along the lines of DRY and SOLID called YAGNI that is designed to help streamline your development efforts in getting things done and not getting paralysed with indecision over what to do.

If you later find that you need to enhance your class, then you will. YAGNI says not to worry so much over it now 'cos you probably won't need to spend that extra effort. Get it done, come back to it if you really need to.

Some say its the opposite of SOLID but really its all about trading off all the factors involved in development, you don't have infinite time or resources (and 'perfect' code never is IMHO).

So here, you say it doesn't need DI complexity... so there's your answer. Don't put it in. Move on to all the other things you have to do.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
  • 1
    I think sometimes it's possible to apply YAGNI over-hastily. The reason that it's okay to not do something upfront is that if your code and test coverage is good, it'll be easy and safe enough to modify later. Dependency inversion (generally via injection) is widely considered a significant part of writing that kind of good, easy-to-modify code. So I think if you want to argue YAGNI, you need to explicitly justify *why* DI is something you don't need to do up-front in this case- different to, say, good naming, loose coupling, DRY, SRP, etc. – Ben Aaronson Apr 08 '15 at 15:50
  • 2
    @BenAaronson true, but then its easy to apply everything over-hastily. In this case, DI in an instance where he's never going to need it, he;s already outlined why he thinks this is the case, and I'd argue to agree that simplicity is more important. Less code means less bugs, more dev productivity and more time to spend down the pub! – gbjbaanb Apr 08 '15 at 16:01
  • Yeah, I'm not saying I disagree with your conclusion, just felt like there's maybe a chunk missing from your argument. Or at least maybe a point to be underscored so that whoever reads your answer doesn't find themselves saying "My code isn't DRY? Screw it, YAGNI!" – Ben Aaronson Apr 08 '15 at 16:03
  • @BenAaronson: just to make it clear for other readers (I am sure you know already): applying YAGNI to DI will not make the code immediately "dirty". However, trying to apply YAGNI to legitimate code duplication will sooner or later strike back on you. – Doc Brown Apr 08 '15 at 18:38
5

If you do not apply DI as long as you do not really need it (not even for unit testing), nothing bad will happen. The code does not become error prone, "overly complicated", or hard to maintain. And if you decide to refactor the dependency out later, it will most probably not be much more effort than doing it now. That's a case where the YAGNI principle applies.

However, what you should check is if you are sure you really do not want to be able to unit test SomeOtherClass in isolation from SomeClass, and if the imposed dependency on the assemblies where SomeOtherClass and SomeClass live will not become a problem. If you are 100% sure that the answer to the former questions is "yes", then you can ignore DI.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
0

The answer like most things is "it depends".

If you want to unit-test the functionality in doSomethingImportantUsingDependency, then you will need to be able to inject the dependency.

However, if all that doSomethingImportantUsingDependency does is some property mapping from the result of your database call, then it would be pragmatic to not bother.

If some other class depends on SomeOtherClass then you can always inject that class instead.

Matthew
  • 1,976
  • 3
  • 18
  • 26
  • http://programmers.stackexchange.com/questions/278572/should-one-use-dependency-injection-even-if-the-class-is-used-only-once?noredirect=1#comment573027_278572 – gnat Apr 08 '15 at 16:38
  • I never took the reading comprehension 101 course. – Matthew Apr 08 '15 at 17:03