Why Refactoring Matters: Practical Tips and Code Smell Solutions
This article, based on the author’s experience and Martin Fowler’s book, explains what refactoring is, why it’s essential, how to spot code smells, and provides concrete principles and JavaScript examples to improve code design, readability, and maintainability without focusing on performance.
This article summarizes the author's development experience and insights from Martin Fowler's "Refactoring, Improving the Design of Existing Code", aiming to help developers understand that refactoring is not as heavy as imagined; it can be simple. Commit a feature, review and refactor.
1. What is Refactoring
Definition: improving the design of existing code.
Specifically, it is a structural adjustment without changing external behavior. Refactoring is not performance optimization; it focuses on readability and extensibility, and its impact on performance can be positive or negative.
2. Why Refactor
2.1. Improve Internal Design
Without refactoring, code design deteriorates over iterations, making development difficult.
The main reasons are: developers modify code for short‑term goals without fully understanding the overall architecture, and perfect upfront design is impossible; only practice reveals the truth.
Therefore, refactoring is essential for rapid, maintainable development.
2.2. Make Code Easier to Understand
Understanding code is a prerequisite for modification; poor naming, complex conditions, and missing comments hinder this.
Good refactoring makes code "self‑explanatory". This benefits collaborative development and future maintenance.
2.3. Increase Development Speed & Ease Error Location
Although refactoring seems extra work, reducing code duplication and improving structure saves time in later development, allowing developers to work more efficiently.
3. Refactoring Principles
3.1. Keep the Current Programming State
Kent Beck's "two hats" metaphor: separate time for adding features and for refactoring; never mix them. When adding a feature, do not modify existing code; when refactoring, do not add new functionality unless absolutely necessary.
Always be aware which hat you are wearing to keep goals clear and progress controllable.
3.2. Controlled Refactoring
Refactoring should be incremental, with each step committed and tested; otherwise you risk an unusable intermediate state.
Every step must be small, tracked by Git, and accompanied by tests to avoid losing the previous working version.
4. Identify Code Smells
Now it is time to review code fragments and eliminate their "smells".
4.1. Mysterious Naming
Unclear variable or function names force developers to spend time deciphering their purpose, often requiring excessive comments.
Good naming should be meaningful, related, and non‑redundant.
Balance brevity and descriptiveness.
Maintain a consistent naming style across the team.
Names should convey relationships without ambiguity.
Good English helps create clear names.
4.2. Duplicate Code
Repeating similar code in multiple places creates maintenance headaches; a change requires editing every copy, increasing the risk of errors.
Extract the common logic into a shared function.
4.3. Long Functions
Functions that exceed a screenful become hard to understand and increase coupling.
According to Tencent's guidelines, a function should not exceed 80 lines.
// before refactoring
function changeList(list) {
console.log('some operation of list');
for (let i = 0; i < list.length; i++) {
// do sth
}
console.log('conditional judgment');
let result;
if (list.length < 4) {
result = list.pop();
} else {
result = list.shift();
}
const today = new Date(Date.now());
const dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
result.dueDate = dueDate;
return result;
}
// after refactoring
function changeList(list) {
console.log('some operation of list');
operationOfList(list);
console.log('conditional judgment');
const result = judgment(list);
result.dueDate = getRecordTime();
return result;
}
function operationOfList(list) {
for (let i = 0; i < list.length; i++) {
// do sth
}
return list;
}
function judgment(list) {
let result;
if (list.length < 4) {
result = list.pop();
} else {
result = list.shift();
}
return result;
}
function getRecordTime() {
const today = new Date(Date.now());
const dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
return dueDate;
}Splitting a long function improves readability and reduces coupling.
4.4. Data Clumps & Long Parameter Lists
Data clumps (magic numbers) are unrelated data grouped together, making them hard to manage.
Group related parameters into a dedicated object.
function addUser(name, gender, age) {
// ...
clumps.officeAreaCode = '+86';
clumps.officeNumber = 13688888888;
return person;
}
// after refactoring
class TelephoneNumber {
constructor(officeAreaCode, officeNumber) {
this.officeAreaCode = officeAreaCode;
this.officeNumber = officeNumber;
}
}
function addUser(person) {
// ...
person.telephone = new TelephoneNumber('+86', '13688888888');
}4.5. Global Data
Global variables increase the difficulty of tracking state changes and debugging.
Encapsulate access through getter/setter functions.
let globalData = 1;
// bad
function foo() { globalData = 2; }
function fuu() { globalData = { a: 1 }; }
// good
const constantData = 1;
let globalData = 1;
function getGlobalData() { return globalData; }
function setGlobalData(newGlobalData) {
if (!isValid(newGlobalData)) { throw Error('Illegal input!!!'); return; }
globalData = newGlobalData;
}
export { getGlobalData, setGlobalData };4.6. Divergent Change
When a function must be modified for different reasons, the code becomes scattered and error‑prone.
function getPrice(order) {
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
}
// after extracting
function calBasePrice(order) { return order.quantity * order.itemPrice; }
function calDiscount(order) { return Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; }
function calShipping(basePrice) { return Math.min(basePrice * 0.1, 100); }
function getPrice(order) { return calBasePrice(order) - calDiscount(order) + calShipping(calBasePrice(order)); }4.7. Shotgun Surgery
Small changes require modifications in many places, increasing the chance of missing a spot.
Encapsulate the related logic in a single module.
class Reading {
constructor(data) {
this.customer = data.customer;
this.quantity = data.quantity;
this.month = data.month;
this.year = data.year;
}
get baseRate() { /* ... */ }
get baseCharge() { return this.baseRate(this.month, this.year) * this.quantity; }
get taxableCharge() { return Math.max(0, this.base - this.taxThreshold()); }
get taxThreshold() { /* ... */ }
}
const reading = new Reading({ customer: 'Evan You', quantity: 10, month: 8, year: 2021 });4.8. For Loops
Traditional for loops can be replaced by array pipeline operations for clearer intent.
// for
const programmerNames = [];
for (const item of people) {
if (item.job === 'programmer') { programmerNames.push(item.name); }
}
// pipeline
const programmerNames = people
.filter(item => item.job === 'programmer')
.map(item => item.name);4.9. Complex Conditional Logic & Merged Conditions
Encapsulate complex conditions into well‑named functions.
// bad
if (!date.isBefore(plan.summberStart) && !date.isAfter(plan.summberEnd)) {
charge = quantity * plan.summerRate;
} else {
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
// good
if (isSummer()) { charge = quantity * plan.summerRate; }
else { charge = quantity * plan.regularRate + plan.regularServiceCharge; }
// perfect
charge = isSummer() ? summerCharge() : regularCharge();4.10. Query vs. Modification Coupling
Separate pure query functions from functions that cause side effects.
function getTotalAndSendEmail() {
const emailList = programmerList
.filter(item => item.occupationalAge <= 2 && item.stars === 5)
.map(item => item.email);
return sendEmail(emailList);
}
function search() { return programmerList.filter(item => item.occupationalAge <= 2 && item.stars === 5).map(item => item.email); }
function send() { return sendEmail(search()); }4.11. Guard Clauses Instead of Nested Conditionals
Use early returns to handle exceptional cases, keeping the main logic less indented.
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separateAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}5. When to Start Refactoring
5.1. Before Adding a New Feature
Inspect the existing codebase; small structural tweaks can make the new feature implementation much easier.
5.2. After Completing a Feature or After Code Review
Refactor when the feature is stable and tests pass; this avoids affecting existing functionality and keeps the codebase clean.
5.3. When Adding New Features Becomes Difficult
If the code is so tangled that adding features is painful, a larger refactoring effort may be required, though sometimes a rewrite is preferable.
6. When Not to Refactor
6.1. Rewriting Is Easier
If rewriting the component is simpler than refactoring, choose rewrite.
6.2. No Need to Understand the Code
If a stable API works flawlessly, you may tolerate its internal messiness.
6.3. Without Team Consensus
When multiple modules depend on the code, inform owners before making changes.
7. Refactoring and Performance
Refactoring focuses on readability and extensibility, not on performance. In most cases, performance differences are negligible; if needed, optimize the hot spots after refactoring.
8. Conclusion
The author is not a "refactoring master"; the article covers common refactoring techniques and thoughts. For deeper study, read Martin Fowler's classic book, which uses JavaScript examples—great for front‑end engineers.
VSCode users can benefit from plugins such as JavaScript Booster or Stepsize that suggest refactoring opportunities.
Now you know what to do: Commit a feature, review and refactor.
9. References
[0] "Refactoring, Improving the Design of Existing Code (2nd Edition)" – Martin Fowler
[1] 24 Common Code Smells and Refactoring Techniques
[2] 6 Useful Front‑End Refactoring Plugins for VSCode
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.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
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.
