Variables - Using constants

by César SantosSep 14th, 20204 min read
Also available in: Spanish

This is the first post in a serie of posts about improving variable usages in general.

Note: Code example in this post is in JavaScript, but the concepts and suggestions can be applied to any language. This example doesn't make sense functionally, but it works to explain the concepts of this post.

Index

These are the topics discussed on each post:

  1. Using constants
  2. Avoiding magic strings duplication
  3. Using variables to simplify code

Using constants

First of all, what is a "magic string"? As Wikipedia's page mentions, magic strings are values that cannot be changed externally. This means they are used internally by your code, and the user has no direct way to modify their values. In summary, they are constants.

You could think of "user types", "document types" or "page identifiers" as some examples of magic strings, where you have a finite number of options, and they are used internally to determine the behavior of your app.

Even if they are called "magic strings", they don't necessarily have to be strings all the time. You could think of "maximum number of characters in a name" or "maximum file size" as examples of magic strings as well.

Who determines those 'magic strings'?

I can think of 2 ways to determine them: a) constraints you have defined in your application (such as database constraints or internal ids); or b) values provided by external services, for example, let's suppose you're using an external service for your login page, and that service told you there will be 3 types of users logging in: 'GUEST', 'USER' or 'ADMIN'.

When do we use 'magic strings'?

They could be used for multiple things, but the most common and simple use case could be to determine which logical path to take in your app.

Let's take a look at the following code:

function onLoad() {
  if (process.env.ENVIRONMENT_ID === 'aGVsbG8gd29ybGQ=') {    console.log('It is "hello world" environment')
  }
}

function uploadFile(file) {
  if (file.size > 10000) {    console.error('File size is too big')
  } else {
    console.log('File is uploding')
  }
}

// Invoking those functions
onLoad()
uploadFile(file)

As you can see there are some random hard-coded values introducing a couple of issues:

  • You have to literally read the code to understand what is expected to happen on each if condition. Yes, this code is pretty simple, and you can understand it right away... but what if the code is not that simple? How would you (or your teammates) know what 'aGVsbG8gd29ybGQ=' stands for?
  • If you need any of those values ('aGVsbG8gd29ybGQ=' or 10000) in another place, you will end up with those values duplicated in multiples places.

Now, let's see how that code could be improved:

// usually exported from a `constants` fileexport const HELLO_WORLD_ENV_ID = 'aGVsbG8gd29ybGQ='export const MAX_FILE_SIZE = 10000
function onLoad() {
  if (process.env.ENVIRONMENT_ID === HELLO_WORLD_ENV_ID) {    console.log('It is "hello world" environment')
  }
}

function uploadFile(file) {
  if (file.size > MAX_FILE_SIZE) {    console.error('File size is too big')
  } else {
    console.log('File is uploding')
  }
}

// Invoking those functions
onLoad()
uploadFile(file)

This way you gain multiple benefits:

  • Only one place where to find those hard-coded values. Easy to change if your constraints change in the future.
  • If the value of the constant is something weird or hard to understand, (for example aGVsbG8gd29ybGQ= above), having the ability to set a name for that value is a big advantage to understand what it is for.
  • You can reuse those constants in other places as needed.
  • You don't need to read the whole code to understand what those values are.

Code standards (Personal preference)

  • If you use the constant in only one file, keep that constant in the same file.
  • If you need the constant across multiple files, create a constants file at a level that makes sense for all files that require it (usually at the feature level).
  • Group constants per feature. Don't create a unique global constants file with all the constants of your application.
  • Write your magic string constants names in Screaming snake case format (all uppercase & separated by _). This helps to identify clearly what is an immutable constant versus not.

Make sure your code is clear and easy for others to understand. Try to make it as reusable and scalable as possible.

You won't be the only person touching that codebase for ever!