At this point, it should be pretty clear for most developers why we don’t like magic numbers.
The presence of magic numbers or other value literals (being numbers, strings or whatever) in code is generally recognized as bad smell. Nevertheless, the default solutions for this don’t always solve the problem completely. In this article I’ll show you the way in which we, at Inaka, prefer to approach this situation regardless of the languages used to build each particular system.
First of all, my solution (as any solution) doesn’t work in all scenarios. For this one to be applicable to your system, the following conditions should be met:
If your system doesn’t meet that criteria, you can confidently skip the rest of the article. See you next week :)
To give you a better picture of the motivation behind the recommended technique, I would like to show you a list of some of the things that I’ve seen written as literals in the code. These are clearly not all the things that might end up as magic literals, but these are the ones that led me to promote the System Settings Method within the company:
You can probably see why it’s not a good idea to keep those things (and many similar others) in code. They tend to change when you least expect them to, e.g. the client wanted to change their branding and so they created a new FB app, usage information determined that for a better UX the size of the pages should be reduced/extended, the owner of the system management email just quit the company or was moved to other team, etc… If those values are hardcoded, changing them requires code changes, tests, pull requests, code reviews, deploys in different environments, QA tests and so on…
There are better ways to implement these things.
Now, before delving into the proposed approach, let’s look at some alternatives. Because, as I said, the problem is well known in general and most languages provide a mechanism to extract those literals from your code nicely. I recall the ones I’ve used:
You see the common pattern there, right? They are all text files where you put all your constants/literals. They are then bundled and released with your system. They are easily editable and usually come with smart mechanisms that allow DevOps to adapt them for different environments where the system will be deployed. The languages/libraries give developers super-easy ways to access the info in those files, too. But editing something in those files may present some challenges:
Of course, each one of those issues can be addressed with different workarounds or tools. What we use in our systems is a more general approach that tries to solve all those issues at once.
So, what do we do? Conceptually, our idea is to include these constants/literals as first class citizens of our system’s model. So, if we’re working in OOP, we add objects/classes to represent these system settings. If we’re working in a functional language, we represent these settings with their own Abstract Data Type, as described here.
In practice, that ends up being persisted in a table in our database. And that table/model has a key and a value, as expected. But that model also includes a mandatory description for each key (thus providing documentation in place) and additional metadata (in that same model or another related one) to keep track of who modified the value and when. It may even include the previous value, so that any change can be easily rolled back.
To edit those values, we add a panel in our Admin Interfaces (usually private web applications that are only accessible to system administrators). That panel may provide more or less flexibility depending on the system and the parameters to be configured. In general, it presents a list of settings, each one with its key, description and an input control where the admin user can edit the corresponding value.
Our systems then grab those values from that database table, although more often than not we add a cache layer on top of that not to be constantly querying the DB for the same values. To deal with that cache layer, we add an internal mechanism for the admin panel to let the server know that it has to refresh the cache. That mechanism (usually, a REST endpoint) is executed whenever a setting is changed.
The first time we came up with this technique, we did it because we were already tired of being called at 1AM (Most of us work from Buenos Aires for companies based on US West Coast) to change a single parameter and deploy the system again. We knew we could tell our clients to change that value in the proper config file and we could teach them how to restart their systems. We actually wrote documentation about that… more than once. That didn’t do the trick: they still felt that config files were somehow our territory. And they were not that far from the truth, actually.
I’m pretty sure many people before us knew about this and many of you after reading this article will think d’uh, man… of course! I’ve been building systems this way for 3 decades already!. But maybe, hopefully, some of you haven’t thought about it yet and it may improve your lives as it improved ours.
This solution is certainly not innovative, but it works!
Clients still call us at strange hours to change something, but now we have the pleasure to tell them “Yes, it’s right there in your system settings page. You can change it yourself!” and then happily and proudly hang up the phone :)