3

Imagine we have a service like the this:

class SomeService {
    // A random pure function that transforms the input
    // without doing any remote calls or any side effects
    function somePureFunction(inputNumber) {
        return inputNumber *2
    }
}

Now we are using it by registering it with a dependency injection and injecting it into various components (classes) in various places.

I don't like such design, because I don't see the point of using DI in such case and instead, I always recommend to create a static Utils/Helper class for such methods that can be accessed statically.

Basically, I am struggling to find good arguments why a static helper class is better in such case and would much appreciate other opinions on this matter.

  • If you already have DI in place, what's the problem with using it for this class too? What benefit would there to changing? If you can't think of good arguments, maybe just don't do it. – jonrsharpe Nov 16 '18 at 12:13
  • 1
    @jonrsharpe, the benefit is a reduction in the complexity of the code. – David Arno Nov 16 '18 at 12:13
  • 3
    @DavidArno I was more interested in what the OP saw as the benefits. It seems like they want to argue for something but don't actually know why, which seems odd. – jonrsharpe Nov 16 '18 at 12:15
  • 1
    When I see a service used via DI, I assume that there are some benefits to use a complicated DI mechanism. E.g. you need to mock services that use remote calls in the classes that use them where DI helps you a lot. And when I see a static method being called, I can safely assume that there are no remote calls done for it and most likely it is a pure function – eddyP23 Nov 16 '18 at 12:20
  • I like the idea that clean code = f( WTFs per minute ) and that is the basis of my belief, but that is exactly why I posted this answer to hear some other opinions – eddyP23 Nov 16 '18 at 12:21
4

Dependency injection requires a trade-off between decoupling parts of the code at the expense of increasing its complexity through creating abstractions. So as a rule of thumb, that increase in complexity has to offer benefits that outweigh the costs.

By injecting dependencies that are mutable or have side effects, we gain obvious benefits. The code becomes easier to test as those side-effects can be mocked out. We control which parts of the system have access to those mutating parts, making the code cleaner, easier to understand and easier to maintain. The increased complexity is easily counteracted by those benefits.

But if a dependency is pure, we do not gain any of those benefits, yet we still incur the increased complexity costs. Would you inject Math.PI (or the equivalent), or access it directly? It's a constant, so there would be no benefit in doing so; it can be made globally accessible. But then surely the same applies to Math.Max()? It's completely deterministic. There would be no benefit to mocking it; no benefit to swapping it out for another implementation of Max. So again it's a complexity cost for no gain. So it too should be globally accessible.

If making Math.Max() globally accessible makes sense. Then likewise, making your somePureFunction globally accessible also makes complete sense. Injecting it again makes the code more complex with no added benefits.

Of course, it's important to emphasise "pure" here. If your function in any way is non-deterministic, has any sort of side effect or in any way mutates the parameters passed to it, it is not pure and so should not be globally accessible. Inject them. Just don't inject pure functions.

5

Dependency injection makes sense when you want to change that dependency. For example, you may want to mock a dependency for testing. For a pure function, you should ask yourself:

  • Is this function some utility that holds universally, e.g. Math.Max()? If so, avoiding the complexity of dependency injection seems absolutely fine.

  • Or does this function supply some service that contains business logic, e.g. calculateSalesTax()? Then, you may very well want to change that service through dependency injection.

The difference between these cases is not always apparent. Sometimes a pure utility like max() is used where the choice of using that function should already be configurable, e.g. as a SelectionStrategy. But in most cases, it is not necessary to get this right immediately. If you are working on an application, you can write simple code now and refactor to use DI later, if the need ever arises. However, an API that is consumed by other people might benefit from adding these extension points early so as not to break backwards compatibility in the future.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.