Fundamentals 24 min read

Mastering Caesar Cipher Refactoring: A Step‑by‑Step Clean JavaScript Guide

This article walks through the complete refactoring of a JavaScript Caesar cipher implementation, explaining the cipher basics, the importance of clean code, and eight incremental refactoring steps that replace magic numbers, extract duplicated logic, simplify conditionals, and improve naming for maintainable, readable code.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
Mastering Caesar Cipher Refactoring: A Step‑by‑Step Clean JavaScript Guide

Introduction

In this series we share techniques for writing highly maintainable code, based on Robert C. Martin’s Clean Code and our own experience.

What is the Caesar Cipher?

The Caesar cipher is a simple substitution cipher that shifts each letter by a fixed number of positions in the alphabet. For example, with a right shift of 6 the alphabet becomes:

Plain:    ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher:   GHIJKLMNOPQRSTUVWXYZABCDEF

Encrypting the classic pangram yields:

Plaintext: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG

Ciphertext: QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD

What is Refactoring and Why?

Refactoring is not a silver bullet, but a valuable weapon that helps you keep code and projects under control.
It is a scientific process that makes existing code more readable, easier to understand, and cleaner, facilitating new features, large applications, and bug fixes.

Reasons to refactor include improving design, enhancing understandability, discovering bugs, fixing legacy code, and providing better consistency for users.

Initial Code

The original implementation provides two functions, cipher and decipher, that shift characters based on ASCII codes. Simple console.assert tests verify correctness.

function cipher(text, shift) {
  var cipher = '';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) + shift > 90) {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift - 26));
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift));
      }
    } else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) + shift > 122) {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift - 26));
      } else {
        cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift));
      }
    } else {
      // blank space
      cipher = cipher.concat(String.fromCharCode(text.charCodeAt(i) + shift));
    }
  }
  return cipher.toString();
}

function decipher(text, shift) {
  var decipher = '';
  shift = shift % 26;
  for (var i = 0; i < text.length; i++) {
    if (text.charCodeAt(i) >= 65 && text.charCodeAt(i) <= 90) {
      if (text.charCodeAt(i) - shift < 65) {
        decipher = decipher.concat(String.fromCharCode(text.charCodeAt(i) - shift + 26));
      } else {
        decipher = decipher.concat(String.fromCharCode(text.charCodeAt(i) - shift));
      }
    } else if (text.charCodeAt(i) >= 97 && text.charCodeAt(i) <= 122) {
      if (text.charCodeAt(i) - shift < 97) {
        decipher = decipher.concat(String.fromCharCode(text.charCodeAt(i) - shift + 26));
      } else {
        decipher = decipher.concat(String.fromCharCode(text.charCodeAt(i) - shift));
      }
    } else {
      // blank space
      decipher = decipher.concat(String.fromCharCode(text.charCodeAt(i) - shift));
    }
  }
  return decipher.toString();
}

console.assert(
  cipher('Hello World', 1) === 'Ifmmp!Xpsme',
  `${cipher('Hello World', 1)} === 'Ifmmp!Xpsme'`
);
console.assert(
  decipher(cipher('Hello World', 3), 3) === 'Hello World',
  `${decipher(cipher('Hello World', 3), 3)} === 'Hello World'`
);

Step 1. Magic Numbers

Replace hard‑coded numbers with named constants.

const NUMBER_LETTERS = 26;
const LETTER = {
  a: 65,
  z: 90,
  A: 97,
  Z: 122,
};

Step 2. Extract Similar Code from if‑else

Move the repeated concatenation logic into a single place, reducing duplication between cipher and decipher.

Step 3. Avoid Using else

Initialize the result variable with a default value and let the conditional branches only modify it when necessary.

Step 4. Merge IF Logic

Combine the two separate if‑else chains into a single conditional that handles both lower‑case and upper‑case ranges.

Step 5. Simplify Algorithm Logic

Reduce the number of nested if statements by consolidating the two possible assignments into a single expression.

Step 6. Encapsulate Conditions

Introduce helper functions that express the out‑of‑range checks for both ciphering and deciphering.

function isOutLowerCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) + shift > LETTER.z
  );
}

function isOutUpperCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) + shift > LETTER.Z
  );
}

function isOutLowerCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) - shift < LETTER.a
  );
}

function isOutUpperCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) - shift < LETTER.A
  );
}

Step 7. Remove if‑else Control Structure

Compute a rotation value once and apply it directly, eliminating the need for separate if‑else branches.

const isOutAlphabet =
  isOutLowerCharacterCipher(text, i, shift) ||
  isOutUpperCharacterCipher(text, i, shift);
const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
const character = text.charCodeAt(i) + shift - rotation;

Step 8. Variable Naming

Rename loop index i to the more descriptive position to convey its purpose.

Final Refactored Code

const NUMBER_LETTERS = 26;
const NO_ROTATION = 0;
const LETTER = { a: 65, z: 90, A: 97, Z: 122 };

function isOutLowerCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) + shift > LETTER.z
  );
}
function isOutUpperCharacterCipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) + shift > LETTER.Z
  );
}
function isOutLowerCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.a &&
    text.charCodeAt(position) <= LETTER.z &&
    text.charCodeAt(position) - shift < LETTER.a
  );
}
function isOutUpperCharacterDecipher(text, position, shift) {
  return (
    text.charCodeAt(position) >= LETTER.A &&
    text.charCodeAt(position) <= LETTER.Z &&
    text.charCodeAt(position) - shift < LETTER.A
  );
}

function cipher(text, shift) {
  let cipher = '';
  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterCipher(text, position, shift) ||
      isOutUpperCharacterCipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) + shift - rotation;
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

function decipher(text, shift) {
  let cipher = '';
  shift = shift % NUMBER_LETTERS;
  for (let position = 0; position < text.length; position++) {
    const isOutAlphabet =
      isOutLowerCharacterDecipher(text, position, shift) ||
      isOutUpperCharacterDecipher(text, position, shift);
    const rotation = isOutAlphabet ? NUMBER_LETTERS : NO_ROTATION;
    const character = text.charCodeAt(position) - shift + rotation;
    cipher = cipher.concat(String.fromCharCode(character));
  }
  return cipher.toString();
}

console.assert(
  cipher('Hello World', 1) === 'Ifmmp!Xpsme',
  `${cipher('Hello World', 1)} === 'Ifmmp!Xpsme'`
);
console.assert(
  decipher(cipher('Hello World', 3), 3) === 'Hello World',
  `${decipher(cipher('Hello World', 3), 3)} === 'Hello World'`
);

Conclusion

The article demonstrates a systematic refactoring process that transforms a naïve Caesar‑cipher implementation into clean, readable JavaScript by eliminating magic numbers, extracting duplicated logic, simplifying conditionals, encapsulating complex checks, and improving naming.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptprogramming fundamentalsCaesar Cipher
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.