Principles and why they’re so important
You may find this less important, or just “do not bother me with this”, but it really pays off, when you stick with some concepts and principles. And it’s not that hard.
Intro
I’m sorry, but this article is reaaaa…aaa…aaaallyy long. Prepare for that — it has sections, so you can read them part by part.
Don’t bother me with the rules
Next is quite a long talk, but I want to put it in the light of practical example, because theory is boring and you would not read it. Probably.
Yes, rules aren’t probably most beloved things around, but in programming, they play quite a big role — let’s pick some piece of code as an example:
What happened now are (probably) number of processes in your head:
- what the fuck is the formatting…
- …because now you’re running tsc in your head…
- …trying to understand what you see
This takes quite a bunch of your mental energy during the day. Another example:
This is exactly the same piece of code, so:
- what the fuck is the formatting (again)…
- …running tsc (again), but now with prettier…
- …trying to update your internal AST to understand the thing
But this article is not about formatting. It’s about rules, principles.
And NOW imagine those two images are from the same repository. You probably know, where I’m walking now. You see that? If there is a rule (in this case for formatting), you can offload a lot of mental effort.
It doesn’t matter, if a brace is here or there, but what’s important, it must be all the times on the same place. Because during a day you would see a lot of code which you must parse and that costs a lot of your energy.
Now you know the basic reasoning I’ve, so let’s deep dive, next are a few principles I like or I would like to comment.
Single Responsibility Principle
This one is first and probably most important one. Let’s get some another example:
- I’ve a repository (for database abstraction, CRUD stuff, queries and so on)
- Basic features: map DTO to internal queries, do mutations (CxUD), provide counts (
SELECT COUNT(*) …
)
When you implement all the stuff, it looks like everything is SRPious, but you’ll end up with quite a bunch of unrelated code:
- Repository contains code which translates DTOs (so code for data fetching)
- There is a code for create/update/delete (this trio is quite OK together)
- You’ve also have code for counting
What you’ll get? Nothing SRPecial but a mess. Even the API from the outside is quite big. Enough complaints, what we can do about that?
Break down pieces, use composition (If you’re curious, this is a historical snapshot of that said repository).
- One Piece for providing query translations (mapping DTOs to query builder or whatever SQL tool used)
- Another for providing data fetching (single entity/collection)
- One for mutations (meaning CRUD without the “R”)
- And the last one for overall API to access all the others
Now all the responsibilities are clearly defined, so you are not messed up, when you want to use this tool.
I’ve provided an example code connected to a specific commit, so it won’t dissapear, but also it will get old by the time. It’s provided only to back my toughts and give you some insight, what SRP means in practice.
That’s all here, the repository example is expressive enough as the idea covers even much simpler situations.
(Random image of a tree to reset your attention)
Dependency Injection
This may be a bit controversal. I’m still on the wave of Node.js, no Java or PHP or whatever. So, really I’m talking about DI? Yes, get over it.
Node.js backend (including Next.js — you know, it’s still Node.js minus Edge stuff) deserves DI too. If you wonder why, read SRP section again.
Examples are cool, se here we go:
I like the repository example, so imagine Repository implementation
- It has dependency on a connection (because you don’t want to export
const connection = …
and then directly import it into “some” file) - You want to refer to a connection by an identifier instead of using direct name (so you can exchange it for something else — cha! Hello Liskov!)
- The same is for the repository itself, but unlikely to happen (unless you like to write tests)
- Now let’s say we’ve a service using said repository — again, we’re not referencing it directly by a file import (you really want to do
new Repository(new Connection())
…? You can use repository identifier from a container to inject it into a service
“But I can write small functions, blablabla”
Yes, you can. Have you ever tried that without any heavy P.I.T.A? Because there is another principle you would break — tight coupling of functions (meaning any kind of executed code).
That’s the thing you really don’t want, because for example, when you’ve a package Foo
and Bar
, you have to create hard dependency between them instead of just saying to a container “bro, I wanna service “Bar”, doesn’t matter where you’ll get it” (soft dependency, because there is FooBar
connecting these two).
There is a bit of overhead using DI container, sometimes it’s hard to find a good quality one, also you need to think in a bit different way, but you can still functions or create serivces like “big brothers have”.
Try it. It’s simpler than you think and it may give you surprisingly cool results, making you more happy with the code. Or hate me if you want.
(More trees. And mountains; do I still have your attention?)
Keep It Stupid Simple
KISS, nice word. Until here I was talking about principles you should use as I think they can help simplify your codebase.
But this one, this is different story. It’s at the end, because after SRP and DI, you’ll end up with the stuff, which may be cleaner, more separated, but it also may be complicated than at the beginning (but less then without them).
For DI, you have to setup a container. You have to define identifiers for your services/functions. When you separate things, there are a lot of files. It has a cost (sorry, no rainbows or unicorns). So we cannot talk about “KISS” as it’s more like “somehow KISS”. It IS simpler, but more complicated.
The take from here is: take the rules, use them, but don’t be hardcore with them as it may hurt your work (or your colleagues may hurt you). As you can see, if you want to take everything literally, some of the principles goes in opposition to each other (like KISS here).
At the end of the day, your clear mind and fresh head is the best tool to use, so you can finally choose, what to use — or how hard to apply different approaches.
If you go through my codebase, you’ll find a lot of places which could break the rules, but overall design stays same. Sometimes it’s much harder to keep the rules on the track than make a workaround.
The Others
This is not a comprehensive guide over SOLID. Nope, because that’s really theoretical material and probably “nobody” can explain them in the real world. Or simple doesn’t care. I’m not telling it’s not important, but I’m just skipping them as those important are already mentioned above.
(Now it’s up to you, which path you’ll take — do you see choices on the picture? — you know I’m joking)
Epilogue
…just because I don’t want to end every story with “Conclusion”
So are they principles and rules important? You’ve some examples here, so now you’re probably thinking about it. Choose responsibly and teach the others. Responsibly. It’s better to have a material to create an opinion than hardcore fans of some opinion.
Now it’s time for you to open your favourite vim… IDE and try all that stuff yourself!