Refactoring: Further discussions on DI
Based on conversation with @thatdeji on Twitter
I received an interesting question about “Refactoring: TF is Dependency Inversion”, which sparked a conversation that I thought to share that as an article with some paraphrasing to focus on relevant context.
TL;DR;
The conversation was about how to tell where DI is necessary, and we touched on topics like Side Effects and Function Predictability.
So here you said their dependency on other modules should be greatly minimized, how about in situations where you need to break them down into sub modules.
Especially when they’re getting bogus and you need to break them into pieces, like let’s say 2–4, you’ll have to import the sub modules in the main one.
Is that like bad cos’ it’s dependent on them?
Me
Ooh that’s an interesting question!
One goal of dependency inversion is to reduce side effects. And side effects are caused by a Function doing something that is unpredictable, usually by referencing something external to your system.
So if a Function depends on other Function, but neither of them cause side effects, they do not need their dependency relationship, inverted. E.g.
const pi = () => Math.PI;
const areaOfCircle = (radius) => pi() * radius * radius;
We can afford to not abstract pi()
, because it is a predictable function. It will always return 3.142
, so it causes no side effects.
At no point, will the value of pi()
change in the future.
However,
const tomorrow = () => new Date(
Date.now() + 24 * 60 * 60 * 1000
);
Here, the value of Date.now()
changes every millisecond, so it definitely causes a side effect cos we cannot predict its value. This function cannot be used in a test as is, cos well, its value cannot be predicted.
So we need to make tomorrow
predictable, by abstracting its dependency on the System’s DateTime.
So I would refactortomorrow
as:
const tomorrow = ({ now = () => Date.now() } = {}) => new Date(
now() + 24 * 60 * 60 * 1000
);
We’ve made now()
into a dependency of tomorrow()
and we can pass a predictable value for now()
during tests. e.g.
tomorrow({
now: () => new Date("2022-01-01")
})
This function is now predictable and causes no side effects.
Function Predictability means that we can accurately tell what a function will return, given a set of arguments. It is popularly known as Function Determinism.
@thatdeji
Okay, so the
tomorrow
function return value won’t change as long as thenow()
function is made predictable, because that changing part has been abstracted.
Me
Yes, that is it.