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.
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: GHIJKLMNOPQRSTUVWXYZABCDEFEncrypting 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
