SECTION 1
Historical Context & Motivation
In the early days of programming, code was written primarily for machines. Programmers punched instructions into cards and fed them into massive computers that filled entire rooms. Readability was an afterthought because humans rarely needed to revisit their code after it ran. As software grew larger and teams started collaborating, developers realized a painful truth: code is read far more often than it is written. A single function might be written once but read dozens or even hundreds of times by different people trying to fix bugs, add features, or understand how a system works.
This realization gave rise to refactoring — the practice of restructuring existing code without changing its external behavior. The term was popularized in the late 1990s, but the underlying idea is simple: clean up your code so that the next person (or future you) can understand it quickly. Think of it like rewriting a confusing essay so the same ideas come through more clearly.
1972 • Structured Programming Movement
1991 • William Opdyke's PhD Thesis
1999 • Martin Fowler's Refactoring Book
2015 • ES6 and Modern JavaScript
The central question that refactoring addresses is this: How do we improve the quality and clarity of code that already works, without accidentally breaking it? This lesson will teach you three powerful refactoring strategies — better names, extracted functions, and consistent formatting — and show you how to apply them in JavaScript.
SECTION 2
Core Principles of Refactoring for Readability
Refactoring for readability rests on a few foundational ideas. Each principle targets a different aspect of what makes code hard to understand, and together they form a complete toolkit for cleaning up messy programs. The golden rule underlying all of them is that the behavior of your program must remain identical before and after refactoring. You are reorganizing, not rewriting logic.
Meaningful Names
x or d with descriptive names like studentAge or calculateTotal.Extract Functions
Consistent Formatting
Behavior Preservation
KEY TAKEAWAY
SECTION 3
Visual Explanation — Before vs. After Refactoring
The following diagram illustrates how the same program logic looks before and after refactoring. On the left, you see a single long function with cryptic variable names and tangled logic. On the right, the same behavior is split into clearly named functions with descriptive variables. Notice that both sides produce the exact same output — only the organization and naming have changed.
Look at the left side of the diagram and imagine you encounter this code for the first time. You would have to read every line, trace the math, and guess what a, b, and c represent. Now look at the right side — the function names tell you exactly what each step does. You can read addTax(getSubtotal(price, qty)) almost like an English sentence. That is the power of refactoring.
SECTION 4
How Refactoring Works — The Three Techniques
Technique 1: Rename Variables and Functions
The simplest refactoring technique is renaming. A good name answers the question "what is this?" or "what does this do?" without requiring the reader to look at surrounding code. In JavaScript, the convention is to use camelCase for variables and functions (e.g., totalPrice) and UPPER_SNAKE_CASE for constants (e.g., MAX_RETRIES). Avoid single-letter names except for simple loop counters like i or j.
| Bad Name | Good Name | Why It's Better |
|---|---|---|
d | elapsedDays | Reveals that the value represents a time duration in days |
temp | temperatureFahrenheit | Specifies what is being measured and the unit |
fn | formatUserName | Describes the action the function performs |
list | studentGrades | Indicates what the array contains, not just that it's a list |
flag | isLoggedIn | Boolean names starting with is/has/can read like yes/no questions |
Examples of renaming variables for clarity
Technique 2: Extract Functions
When you notice a block of code inside a larger function that performs one distinct task, you can extract it into its own named function. The original code is replaced with a function call. This achieves two things: the extracted function gets a descriptive name that documents what it does, and the original function becomes shorter and easier to follow. A good rule of thumb is that if you need to write a comment explaining what a block of code does, that block should probably be its own function — the function name replaces the comment.
Technique 3: Consistent Formatting
Formatting refers to indentation, spacing around operators, placement of curly braces, and blank lines between sections. Inconsistent formatting forces your brain to constantly re-adjust, like reading a book where the font size changes every paragraph. In JavaScript, common conventions include using 2 spaces per indentation level, placing opening braces on the same line as the statement, and adding blank lines between function definitions. Tools like Prettier can automatically format your code, but understanding the principles behind formatting helps you write cleaner code from the start.
The Rule of Three
SECTION 5
Recognizing Code Smells — What Needs Refactoring?
Developers use the term code smell to describe patterns in code that suggest something could be improved. A code smell doesn't mean the code is broken — it runs fine — but it hints that the code will be hard to understand, maintain, or extend. Learning to recognize these smells is like developing a spidey-sense for code quality.
The key insight from this diagram is the refactoring safety net at the bottom. You should never refactor everything at once. Instead, make one small change, test that the output hasn't changed, and only then move on to the next improvement. If something breaks, you only need to undo the last small change rather than trying to untangle a massive rewrite. This discipline is what separates refactoring from rewriting.
SECTION 6
Worked Example — Refactoring a Grade Calculator
Let's walk through a complete refactoring of a JavaScript function that calculates a student's letter grade from an array of scores. We'll apply all three techniques — renaming, extracting functions, and formatting — one step at a time, verifying behavior after each change.
Refactoring a Grade Calculator
- 1
Step 1 — Examine the Original Code
Here is the original, hard-to-read function:function g(s){let t=0;for(let i=0;i<s.length;i++){t+=s[i];}let a=t/s.length;if(a>=90){return 'A';}else if(a>=80){return 'B';}else if(a>=70){return 'C';}else if(a>=60){return 'D';}else{return 'F';}}This works, but the function namegand variabless,t, andatell us nothing about what's happening. - 2
Step 2 — Rename Variables and the Function
Replace every cryptic name with a descriptive one.gbecomesgetLetterGrade,sbecomesscores,tbecomestotalPoints, andabecomesaverage. Test: callinggetLetterGrade([85, 92, 78])still returns'B'. ✓All names now describe their purpose. Behavior unchanged. ✓ - 3
Step 3 — Apply Consistent Formatting
Add proper indentation (2 spaces), put each statement on its own line, and add blank lines between logical sections. Place opening braces on the same line as the keyword. The code is now visually scannable:function getLetterGrade(scores) { let totalPoints = 0; for (let i = 0; i < scores.length; i++) { totalPoints += scores[i]; } let average = totalPoints / scores.length; if (average >= 90) { return 'A'; } else if (average >= 80) { return 'B'; } else if (average >= 70) { return 'C'; } else if (average >= 60) { return 'D'; } else { return 'F'; } }Test again — same output. ✓Formatting is consistent throughout. Behavior unchanged. ✓ - 4
Step 4 — Extract Helper Functions
The function does two distinct jobs: computing the average and converting the average to a letter. Extract each into its own function:function calculateAverage(scores) { let totalPoints = 0; for (let i = 0; i < scores.length; i++) { totalPoints += scores[i]; } return totalPoints / scores.length; } function convertToLetter(average) { if (average >= 90) return 'A'; if (average >= 80) return 'B'; if (average >= 70) return 'C'; if (average >= 60) return 'D'; return 'F'; } function getLetterGrade(scores) { const average = calculateAverage(scores); return convertToLetter(average); }The main function is now just two lines that read like plain English. Each helper function can also be tested independently.Final result: three small, readable, single-purpose functions that produce identical output to the original.
Did You Notice?
let average to const average in the final version because the value is never reassigned. Using const signals to readers that this value won't change, which is another small readability win.SECTION 7
Benefits and Limitations of Refactoring
Refactoring is one of the most valuable skills a programmer can develop, but like any tool it comes with trade-offs. Understanding both the benefits and the limitations helps you decide when and how much to refactor.
| Benefits | Limitations / Risks |
|---|---|
| Easier to understand — new team members or your future self can read the code quickly | Takes time — refactoring doesn't add new features, so it can feel unproductive in the short term |
| Easier to debug — small, named functions make it clear where a bug lives | Risk of introducing bugs — even careful changes can break something if you skip testing |
| Easier to extend — adding a feature is simpler when the existing code is well-organized | Over-refactoring — splitting code into too many tiny functions can make it harder to follow |
| Reduces duplication — shared functions replace copied code, so fixes only need to happen once | Subjective naming — reasonable people can disagree about the 'best' name for a variable or function |
Benefits vs. limitations of refactoring for readability
KEY TAKEAWAY
SECTION 8
Connection to Advanced Practices
The refactoring skills you're learning now — renaming, extracting functions, and formatting — are the foundation for more advanced software engineering practices that you'll encounter in future courses and professional work. Understanding where these basics lead can motivate you to build strong habits now.
| What You Learn Now | Where It Leads | Why It Matters |
|---|---|---|
| Rename variables and functions | Design patterns and API naming conventions | Professional codebases have strict naming standards that entire teams follow |
| Extract functions for single tasks | Single Responsibility Principle (SOLID) | In advanced OOP, every class and module should have one reason to change |
| Format consistently | Linters (ESLint) and formatters (Prettier) | Industry teams use automated tools that enforce formatting rules across thousands of files |
| Test after each change | Test-Driven Development (TDD) and CI/CD | Professional teams write automated tests that run every time code changes |
How basic refactoring skills connect to professional practices
As you progress in computer science, you'll also encounter automated refactoring tools built into code editors like VS Code. These tools can rename a variable everywhere it appears with a single shortcut (F2 in VS Code), extract a selection into a function automatically, and flag code smells with colored underlines. Learning to refactor manually first gives you the judgment to know when these tools are making the right suggestions and when they're not.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
Question 2: BASIC CALCULATION
let a = 15;
let b = a * 4;
let c = b > 50;
Provide your renamed version and explain each name choice.Show answer
const itemPrice = 15;
const totalCost = itemPrice * 4;
const isOverBudget = totalCost > 50;
itemPrice reveals that a was a price. totalCost shows the result of the multiplication. isOverBudget uses the is prefix convention for booleans. Note that the actual values and logic are unchanged — 15 × 4 still equals 60, and 60 > 50 is still true. We also used const since none of these values are reassigned.Question 3: INTERMEDIATE
function p(arr){let r=[];for(let i=0;i<arr.length;i++){if(arr[i]%2===0){r.push(arr[i]*arr[i])}}return r}Show answer
function isEven(number) {
return number % 2 === 0;
}
function square(number) {
return number * number;
}
function getSquaresOfEvens(numbers) {
const results = [];
for (let i = 0; i < numbers.length; i++) {
if (isEven(numbers[i])) {
results.push(square(numbers[i]));
}
}
return results;
}
We applied all three techniques. The function and variable names are descriptive (getSquaresOfEvens, numbers, results). The even-check and squaring logic were extracted into helper functions. Formatting uses consistent 2-space indentation and each statement is on its own line. The output for any input array is identical to the original.Question 4: APPLIED
function s(w, z) {
let x = 0;
if (w <= 5) { x = w * 2.5; }
else { x = w * 4.0; }
if (z) { x = x + 15; }
return x;
}
Refactor this code so the team can understand it. Explain what each magic number probably represents and how you decided on the names.Show answer
const LIGHT_PACKAGE_LIMIT = 5;
const LIGHT_RATE_PER_POUND = 2.5;
const HEAVY_RATE_PER_POUND = 4.0;
const EXPRESS_SURCHARGE = 15;
function calculateRatePerPound(weightInPounds) {
if (weightInPounds <= LIGHT_PACKAGE_LIMIT) {
return LIGHT_RATE_PER_POUND;
}
return HEAVY_RATE_PER_POUND;
}
function calculateShippingCost(weightInPounds, isExpress) {
let cost = weightInPounds * calculateRatePerPound(weightInPounds);
if (isExpress) {
cost = cost + EXPRESS_SURCHARGE;
}
return cost;
}
Named constants replace every magic number, so future developers can update rates without guessing. The boolean parameter z became isExpress with the is prefix convention. The rate selection logic was extracted into a helper function. Both versions produce the same cost for any weight and express combination.Question 5: CRITICAL THINKING
Show answer
// Before extraction:
let total = price * quantity;
let tax = total * 0.08;
// Extracted function:
function calculateTotalAndTax(price, quantity) {
const total = price * quantity;
const tax = total * 0.08;
return { total, tax };
}
const { total, tax } = calculateTotalAndTax(price, quantity);
By returning an object with both values, we keep the behavior identical. Another approach is to ask whether the two modifications really belong together. Sometimes the fact that a block modifies two things is itself a code smell — perhaps it should be split into two separate extractions. The key principle remains: test the output after extraction to confirm identical behavior.SUMMARY
Lesson Summary
Summary
Refactoring means restructuring existing code to improve its clarity without changing what the program does. The three core techniques are meaningful naming (replacing cryptic identifiers like x with descriptive ones like studentAge), extracting functions (pulling distinct tasks into their own named functions so each one does one thing well), and consistent formatting (using uniform indentation, spacing, and brace placement throughout your code).
The golden rule of refactoring is behavior preservation — the same inputs must produce the same outputs before and after every change. Recognize code smells like mystery names, long functions, magic numbers, and duplicated code as signals that refactoring is needed. Always work in small steps and test after each change to maintain the refactoring safety net. These foundational habits connect directly to professional practices like design patterns, linters, and test-driven development.