DEV Community

Rubén Alapont
Rubén Alapont

Posted on • Edited on

Magic Numbers and Magic Strings: It's time to talk about it

In the world of JavaScript, it's common to come across code snippets that use literal numeric values or string literals, known as "magic numbers" and "magic strings". These literal values make our code less readable, more error-prone, and difficult to maintain as it evolves over time. In this article, we will explore best practices for eliminating magic numbers and magic strings in JavaScript.

What are Magic Numbers and Magic Strings?

Magic Numbers are literal numeric values that are used directly in code without a clear explanation of their meaning. For example:

function calculatePrice(total) {
  if (total > 1000) {
    return total * 0.9; // 0.9 is a magic number
  } else {
    return total * 0.95; // 0.95 is another magic number
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers 0.9 and 0.95 are magic numbers because their meaning is not clear. If someone else reads this code in the future, it will be difficult to understand their significance without additional context.

Similarly, Magic Strings are literal string values that are used directly in code without a clear explanation. For example:

function getErrorMessage(code) {
  switch (code) {
    case "ERR001":
      return "Connection Error"; // "ERR001" is a magic string
    case "ERR002":
      return "Authentication Error"; // "ERR002" is another magic string
    default:
      return "Unknown Error";
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the string literals "ERR001" and "ERR002" are magic strings because their meaning is not understood without additional context. This makes it difficult to maintain and extend the code in the future.

Eliminating Magic Numbers and Magic Strings

The most effective way to eliminate magic numbers and magic strings is to assign them descriptive names and store them in variables or constants. This improves code readability and maintainability as the values have clear meanings.

Let's see how we can improve the previous example by eliminating magic numbers and magic strings:

const LARGER_DISCOUNT = 0.9;
const SMALLER_DISCOUNT = 0.95;

function calculatePrice(total) {
  if (total > 1000) {
    return total * LARGER_DISCOUNT;
  } else {
    return total * SMALLER_DISCOUNT;
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, we have assigned the values 0.9 and 0.95 to the constants LARGER_DISCOUNT and SMALLER_DISCOUNT, respectively. Now, it is much clearer what these values represent, improving readability and avoiding confusion.

For the case of magic strings, we can do something similar:

const CONNECTION_ERROR_CODE = "ERR001";
const AUTHENTICATION_ERROR_CODE = "ERR002";

function getErrorMessage(code) {
  switch (code) {
    case CONNECTION_ERROR_CODE:
      return "Connection Error";
    case AUTHENTICATION_ERROR_CODE:
      return "Authentication Error";
    default:
      return "Unknown Error";
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we have assigned the string literals "ERR001" and "ERR002" to the constants CONNECTION_ERROR_CODE and AUTHENTICATION_ERROR_CODE, respectively. Now, the meaning of these error codes is much clearer, improving the maintenance and understanding of the code.

Advantages of Eliminating Magic Numbers and Magic Strings

By eliminating magic numbers and magic strings in our code, we gain several advantages:

  1. Readability: The code becomes more readable and understandable for other developers. Descriptive variable or constant names help

understand the purpose and meaning of the values used.

  1. Maintainability: By assigning descriptive names to values, the code becomes easier to maintain and update in the future. If we need to modify a value, we only need to update the corresponding variable or constant.

  2. Error Prevention: By removing literal values from the code, we reduce the possibility of making errors. By using variables or constants, we avoid writing incorrect or unexpected values.

  3. Reusability: By assigning descriptive names to values, we can reuse them in different parts of the code. This improves consistency and facilitates changes if needed.

Conclusion

Magic numbers and magic strings are common but not recommended practices in JavaScript. By using literal values directly in our code, we sacrifice readability, maintainability, and expose ourselves to potential errors.

By using variables or constants with descriptive names, we can eliminate magic numbers and magic strings, improving the quality of our code. By assigning clear and meaningful names to the values used, we facilitate understanding and maintenance of the code over time.

As senior programmers, it is our responsibility to promote best practices and ensure that our code is readable, maintainable, and free of errors. Eliminating magic numbers and magic strings is an important step in that direction.

And hey, if you enjoyed this dive into the world of Node.js and want more insights into product thinking and development, swing by ProductThinkers.com. It's a treasure trove of ideas, tips, and tricks for the modern developer. See you there!

Until next time, happy coding, and may your data streams always flow smoothly and your pipes never leak! 🌊🔧🚀

Top comments (5)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

In this example, the numbers 0.9 and 0.95 are magic numbers...

So is 1000

Collapse
 
fjones profile image
FJones

Indeed that particular example screams for at the very least another constant LARGE_DISCOUNT_THRESHOLD, but I would actually argue that this is one of many cases where constants obscure readability more than refactoring out the two parts of the logic: 1. determining the discount and 2. calculating the resulting price.

Much more readable - despite forsaking the mantra of removing magic numbers:

function calculatePrice(total) {
    const discount = determineDiscount(total);
    return total * discount;
}

function determineDiscount(total) {
    if (total > 1000) {
        return 0.9;
    }
    else {
        return 0.95;
    }
}
Enter fullscreen mode Exit fullscreen mode

Arguably, with OO patterns, this should be externalised to a configuration, but this already separates the concerns. It also allows us to write separate tests for the discount and whether it calculates the resulting price correctly.

Collapse
 
moopet profile image
Ben Sinclair • Edited

I'd say go further. If someone said you had a 5 or 10% discount (which is what this is effectively doing) you wouldn't expect to multiply the discount by the total.

const LARGE_DISCOUNT = 0.1;
const SMALL_DISCOUNT = 0.05;

function calculatePrice(subtotal) {
  const discount = getPriceBasedDiscount(subtotal);

  return subtotal - discount;
}

function getPriceBasedDiscount(subtotal) {
  if (subtotal > 1000) {
    // By Grabthar's hammer, what a saving.
    return subtotal * LARGE_DISCOUNT;
  }

  return subtotal * SMALL_DISCOUNT;
}
Enter fullscreen mode Exit fullscreen mode

Use constants and human-style calculations.

Collapse
 
hendrikras profile image
Hendrik Ras

Good read. Had heard about magic numbers before but I guess the same goes for strings as well. Thanks!

Collapse
 
fjones profile image
FJones

Strings should not be magic, in languages that support enums. The example given here is exactly that: Using constants for the functionality that an enum would provide. Those should be turned into constants where necessary (i.e. JavaScript), but otherwise are an indication of a code smell.

Most other strings are either user-facing (and thus should ideally be externalised into i18n management), or things like log messages, which actually shouldn't be moved to constants to preserve easy access and to avoid modifying a reused constant to alter semantics in a different case.