Developer Dark Arts: Magic Strings

In this post I am going to try and explain what magic strings are, why they are bad, and how to refactor them away.

Why do I care?

Throughout your journey as developer you will inevitably find yourself starting out on a new project. Most of the time it is an existing code base that you will have to sift through, maintain, and add features to. This is the best time to identify anti-patterns, in this case magic strings, and offer up some best practices.

What the heck is a "magic" string?

// animalType.js
export const AnimalType = {
  rabbit: "rabbit",
  pigeon: "pigeon",
  snake: "snake",
};

// tricks.js
import { AnimalType } from "./animalType";

const trick = {
  animalType: AnimalType["rabbit"],
  description: `Pull ${AnimalType["rabbit"]} out of hat`,
};

// favorites.js
import { AnimalType } from "./animalType";

const animal = AnimalType.rabbit;

You may have seen code similar to above. Longtime developers might be able to sniff out the code smell. There are a handful of problems. All of are rooted around the magic string `"rabbit"`.

❌ We're using a string to access props on the `AnimalType` object in some of the places

❌ Re-typing the same string multiple times is error prone

❌ Find & replacing `rabbit` is error prone because of string vs dot notation uses

❌ Modern editors' autocompletion results are polluted with string values

What qualifies this as a "magic" string you ask? It's magic because of the string value `"rabbit"`.

Let me explain.

A magic string is a string (or number) whose value is seemingly derived out of thin air and is lacking any extra information to understand its origin. Its value is arbitrary. As arbitrary as a rabbit being pulled from a magician's hat. -- me just now

Why is this bad though?

There are some pitfalls to writing your coding like this.

Let's say for a moment that our dev manager went to a conference recently and brought back some new patterns for your team to implement. One of which is that we upper case all of our type values so they are easier to identify when reading code.

In this case we would update our code to the following:

// animalType.js
export const AnimalType = {
  RABBIT: "RABBIT",
  PIGEON: "PIGEON",
  SNAKE: "SNAKE",
};

// tricks.js
import { AnimalType } from "./animalType";

const trick = {
  animalType: AnimalType["RABBIT"],
  description: `Pull ${AnimalType["RABBIT"]} out of hat`,
};

// favorites.js
import { AnimalType } from "./animalType";

const animal = AnimalType.RABBIT;

Here we update the `"rabbit"` value to `"RABBIT"` in a bunch of places. In a real world scenario the "rabbit" string would have to be updated everywhere it is referenced. Every single reference in every file that uses `AnimalType`. We'd also have to do this for `"pigeon"` and `"snake"`. Imagine if their values were more common terms like `"name"` or `"type"`. Good luck find/replacing those. Super gross 🤮.

How can we improve this?

In a perfect world the string `"RABBIT"` would be defined once.

Let me show you:

// animalType.js
const RABBIT = 'RABBIT';
const PIGEON = 'PIGEON';
const SNAKE = 'SNAKE';

export const AnimalType = {
  RABBIT,
  PIGEON,
  SNAKE,
};

// tricks.js
import { AnimalType } from "./animalType";

const trick = {
  animalType: AnimalType.RABBIT,
  description: `Pull ${AnimalType.RABBIT} out of hat`,
};

// favorites.js
import { AnimalType } from "./animalType";

const animal = AnimalType.RABBIT;

Why is this better?

✅ We've defined our string values in one place with names that are informational

✅ Everywhere you see the `RABBIT` variable you know that its actual value is an implementation detail. You only care that you're using the appropriate `AnimalType.<type>`.

✅ The string value for `RABBIT` is defined once. Need to make a change to its value? Update that single line of code. No more find/replace nightmares.

✅ Modern editors autocomplete `AnimalType` without polluting the general results

Conclusion

Hopefully I've done a decent job explaining why magic strings are ill-advised and how you can eliminate some of the issues they cause. Follow these guidelines and the you of tomorrow will thank you. Code readability and ease of maintenance for the win.


Today's post was brought to you by VSCode's "remove dead imports and sort" shortcut: `Option+Shift+o`