Inaka’s approach at removing literals from code
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:
- The system must include a database, key/value store or some other persistency layer.
- The system must include an admin interface with access to that persistency layer.
- The system should not have super-tight real-time constraints. If that’s the case you should look for compile-time solutions, mine is a run-time one.
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:
- User Credentials / Application Tokens: when dealing with 3rd-party APIs, like Facebook or Twitter (especially if this involves OAuth), you usually have to register an app and get an app token that you have to use in all your requests. That app token or user id or app id may seem to be constant in the eyes of developers.
- Page Sizes or other API configurations: for example, when providing or consuming paginated APIs, the page size tends to be seen as a constant as well.
- Default Values: When your app provides default values to present to your users when they’re about to input something, it’s sometimes tempting to write those defaults directly in code.
- Internal System Configuration: For example, the email to which the system will report errors, should they happen. It might seem that keeping that value in the code is not such a bad idea, because it will be used all the time and the end users will not see it or be affected by it. That email might even be just your email after all.
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.
The Usual Suspects
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:
- Visual Basic’s ini files. Oh yeah, baby! I wrote VB6 code for a couple of years, too.
- C# / ASP.NET’s resource files (resx, if I recall correctly).
- Ruby’s .env and secret files.
- Erlang’s sys.config files.
- Elixir’s config.exs files.
- Swift / Objective-C .plist files.
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:
- It usually requires system restart and/or redeploy. If not, at least the edited file should be deployed and the configuration reloaded.
- The files are not generally accessible to non-technical people, who are most of the times the ones requiring the change. To change a parameter requires the intervention of a DevOps or SysAdmin individual.
- Even when sometimes added through comments, documentation on each field is not required and sometimes neglected.
- More often than not, no auditory trails are maintained about the editing of those files.
- In distributed systems you have to distribute the files to all nodes, too.
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.
- Settings are now accessible to non-technical users. They don’t need to ask DevOps to change something.
- We are following the 7 Heuristics for Development by correctly modeling all entities of our domain.
- We can easily add all the metadata we want to the setting objects (like descriptions, previous values, etc.).
- It’s easy to keep an auditory record of all changes. Sometimes we can just use external libraries like Ruby’s PaperTrail
- The need for rebuilds and redeploys is drastically reduced. It’s not eliminated completely (see Downsides below) but most of the times it’s not needed anymore since the server can just pick up the new value whenever it needs it.
- Developing systems this way requires less effort after the system is delivered. In other words, the system owners can manage it on their own, which is something both the system owners and the devs should expect.
- Settings are now accessible to non-technical users. They can seriously mess the system up by mistake. Great power comes with great responsibility.
- There are some settings that can’t be stored in a database table or key/value store (e.g. the database url and port), so configuration files or other mechanisms must still be used for that.
- The initial values for all settings still need to be included somewhere (e.g. in Ruby, we still need to add all the used-to-be-literals to the seeds.rb file and sometimes still get them from the .env file, too).
- Altering some values may require a server restart (e.g. the TCP port in which it’s listening). For those values you need to carefully consider if they should not be in this table at all or if altering them should trigger a restart mechanism.
- Developing systems this way requires more upfront effort.
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 :)