One thing that has always been a great pattern in software is constants. I’ve always had a “love it then hate it then love it then…” relationship with constants. They felt helpful, but I felt like they weren’t doing as much as, or quite exactly, what I wanted. There is a better way indeed! It wasn’t until I started rereading “Structure and Interpretation of Computer Programs” from MIT Press (a great read) that I realized this. That way is to not just get the constant value as an export from a file, but also do checks against it with a method, typically provided from the same file.
Before detailing the difference in pattern, let’s first introduce the problem.
Why constants at all?
We will discuss two common problems I have seen that constants are used to solve. Namely, to implement a sort of pseudo-typing, and to replace magic values such as strings or numbers. In this article we will focus on magic strings, as I have found them particularly common and annoying. Magic strings are what many call the usage of an explicit string value used in code, such as state.view === ‘some-view’ (the same applies to all situations, such as props.view, $scope.view, etc).
While this is pretty explicit, this is not really reusable. If the property on state changes name, every instance where that check happens must change. Likewise, if we decide that ‘some-view’ is not a good name, every check must change.
This is why we use constants in these situations. If we have an object called “views” and give it properties for each value, the string value for each property can be changed without needing to change the actual checks. I personally have found this to be very useful for defining the constants for a lot of my view configuration and states, such as if ( view === editing ) {}, or if ( user.type === teacher ) {} (as opposed to user.type === student).
Why this is still not enough
This is not quite great, as if the property name whose value we are checking against the constant changes, or the constant variable name changes, we still need to change every place where this condition appears. However we have decreased the rate at which we must make changes as the constant itself is now a variable we can refer to. In other words, we no longer have to change every check if “teacher” becomes “educator”.
This is nice! We have eliminated one of the two sources of issues by creating an abstraction over the values themselves. We have also done so in a way that allows us to define a single location where all constants of this kind exist. This makes it easier to figure out what options already exist and were intended to exist by the developer.
How we can vastly improve constants
We can add another layer of abstraction by adding methods that do the checks for us, and in the process make our code cleaner and easier to work with and look at while adding very little overhead. This may seem obvious to many but the idea of doing this slipped my mind for a long time, so hopefully this will remind some people and teach others about this simple technique.
Put as simply as possible, use constants, but never openly. You should define constants, explicitly (preferably as properties of an “enum” object, like what TypeScript provides), but also write methods that do the actual check for you.
These methods should do nothing other than determine if the value given is equivalent to a particular constant’s value. It should not know about or consider any other properties of a given object other than what is necessary to do the check. This keeps the method simple and able to be used in any case where this constant might be used regardless of the object being compared. It also means we can export this method as a normal ES6 method from a file. This is superior to doing “open” (as in, in the code where the constant is used visibly) because we never have to know if there even is a constant, nor anything about the particular argument we are checking the constant against.
To view a simple example of without constants, with open constants, and with abstracted constants, checkout the contents of this gist.
With this approach, we can change the name of the property we are checking, the constant’s name, or the value of the constant separately without changing anything else.
This will work for any level of check, as there is no reason we cannot expand the complexity of this to involve multiple property checks against an object instead of a primitive string value. The important thing here is the abstraction used.
Happy abstracting!
Top comments (0)