November 01 2021
Two questions for you:
If the answer to any is NO, this post might help :-)
Here’s one simple but (sadly) often ignored principle in software development: SRP.
That reads: Single Responsibility Principle.
And that means: every class, every function should have one and only one responsibility.
Some examples:
This is a common mistake that violates SRP:
class Player
{
void LevelUp()
{
Backend.Instance.SendLevelUp();
}
}
You see that Backend.Instance access?
This access violates the Single-Responsibility Principle.
The LevelUp function should just do a level up. It is not part of its job description to find a reference to a backend system (in this case, the singleton).
So this function should just invoke the SendLevelUp function through a backend reference that someone passed it.
This is why breaking SRP in this manner is a problem:
More typical examples of SRP violation:
I can sum it up this way: finding a reference to an object is not your class/function’s problem.
So, whose problem is it then?
Finding object references is the Dependency Injector’s problem.
To keep it short, a DI system does two things:
Think of a DI system as the table of contents of a book. A ToC with a list of chapters ( Backend) and their page/location (0x123123).
This is how you 1. Keep track of your references:
Injector.Bind<IBackend>(new Backend());
Injector.Bind<ILogger>(new LoggerFile());
// ...
And this is how you 2. Inject your references into your objects:
class Player
{
[Inject] private IBackend _backend;
void LevelUp()
{
_backend.SendLevelUp();
}
}
// ...
// ...
Injector.Inject(myPlayerObject);
That’s it.
When it comes to choosing a DI system, there are many third-party libraries out there like Zenject and StrangeIoC.
However, I have a big problem with most…
The main problem I have is that they are not simple, but rather quite complex.
In other words, most DI implementations don’t follow the KISS approach (KISS = Keep it Simple Stupid).
It’s not uncommon to see DI systems with 20k lines of code or more (I’m looking at you, Zenject).
And this is a huge problem because of the effort and time it takes to:
I used Zenject a few times.
Can you guess what happened after running into problems?
I spent DAYS understanding and debugging its code base. Several times.
How much time did I save by using Zenject?
None. In fact, it was negative value. All in all, I spent more time working on a DI system than the DI system working for me.
Not great 😡
So, what to do about it?
Here’s a piece of advice that you can feel free to ignore (and eventually regret):
Every time you evaluate using a third-party library in your PRODUCTION-level project, investigate it well.
Ask yourself these questions about any library you want to use:
It might be 10 minutes, it might be 1 hour. It depends on the system we’re talking about.
At its essence, the key part is this: you want to Keep It Simple Stupid.
Ever since I spent days working on a DI system rather than the DI system working for me, I adhere pretty much to the KISS rule whenever I can.
Sure, that’s not always the case with libraries like Photon or engines like Unity, but those are big for a reason.
A Dependency Injection system can be and should be simple.
Again, simple means:
Following the KISS principle is a professional matter.
So no more bells and whistles, thanks.
You can watch the full lesson on my approach to dependency injection on the module 2021.10 of the PerformanceTaskforce.
This lesson includes everything you need to quickly get started:
Enroll in the Unity Performance Taskforce and head to module 2021.10.
(& grab some popcorn :-))
Ruben