4

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.

asked Nov 16, 2018 at 11:55
5
  • 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. Commented Nov 16, 2018 at 12:13
  • 1
    @jonrsharpe, the benefit is a reduction in the complexity of the code. Commented Nov 16, 2018 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. Commented Nov 16, 2018 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 Commented Nov 16, 2018 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 Commented Nov 16, 2018 at 12:21

2 Answers 2

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.

answered Nov 16, 2018 at 12:13
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.

answered Nov 16, 2018 at 14:43
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.