Why High-Quality Code Matters: Principles, Bad Smells, and Refactoring Techniques
This article explains why maintainable, robust, and testable code is essential for front‑end development, shares insights from renowned programmers, defines key quality metrics, outlines SOLID and KISS design principles, identifies common code smells, and demonstrates practical refactoring patterns with real JavaScript examples.
Why Do We Need High-Quality Code?
Have you ever spent a long time reading source code just to fix a simple feature?
Have you seen a front‑end page go white because an exception wasn’t handled?
Have you needed to refactor a large codebase without any test cases?
These scenarios correspond to the code‑quality indicators of maintainability , robustness and testability . Code quality therefore directly affects software delivery quality.
How Renowned Programmers View High‑Quality Code
Bjarne Stroustrup
I like elegant and efficient code; the logic should be straightforward so defects are hard to hide, dependencies should be minimal, error handling should follow a layered strategy, and performance should be optimal to avoid reckless optimisations.
Grady Booch
Clean code is simple and direct, like beautiful prose. It never hides the designer’s intent and is filled with clear abstractions and straightforward control statements.
Ward Cunningham
If every routine feels intuitive, the code is clean. When the language seems crafted for the problem, the code is beautiful.
Code‑Quality Metrics
Maintainability
Maintainability measures how easy it is to repair and improve a system. Repairability is the ability to recover from failures, while improveability is the ability to add new features.
Readability – Robert Gunning’s Formula
Sentence Structure
Shorter sentences increase readability.
Fog index: the more abstract and difficult the words, the lower the readability.
Human‑interest content improves readability.
These principles, originally for news, also apply to code readability.
Self‑Descriptive Naming
Bad example: let d; // time elapsed in days Improved example:
let elapsedTimeInDays;
let daysSinceCreation;Avoid Misleading Names
Ensure type accuracy (e.g., a hash table should not be named with a List suffix).
Avoid overly similar names such as ControllerForEfficientHandlingOfStrings vs ControllerForEfficientStorageOfStrings.
Do not differentiate meaninglessly (e.g., getActiveAccount() vs getActiveAccountInfo()).
Maintain semantic consistency across classes.
// Do not use wrong type naming
const accountList = {};
// Do not use singular form for arrays
const account = [];Extensibility & Reusability
Good design allows new functionality to be inserted without over‑engineering the code.
Fine‑grained encapsulation and the Single‑Responsibility and Open‑Closed principles are essential.
Robustness (Resilience)
Robustness is the ability of software to survive erroneous input, disk failures, network overloads, or attacks without crashing.
Stability
Stability is closely tied to boundary handling and exception handling.
Defensive Programming
Defensive programming ensures that unexpected usage does not damage functionality.
Use good coding style and reasonable design.
Think carefully before writing each line; anticipate possible errors.
Never trust anyone , including user input and backend responses.
Clarity is more important than clever brevity.
Example of unsafe optional chaining:
// {} is non‑null but lacks sort method
(result?.response?.data || []).sortSafer conversion using lodash:
toArray(result?.response?.data).sortException Handling
Front‑end exception handling should keep the page functional and limit the impact scope.
Wrap potentially failing code in try‑catch.
Use ErrorBoundary to show a fallback UI instead of a white screen.
Guard variable access, e.g., s?.length instead of s.length.
Performance
General Optimisation
Common techniques: space‑for‑time trade‑off, parallelism, reducing computation.
// Reduce double loop to O(1) lookup
newFields.forEach((field, index) => {
const matchCol = newCols.find(col => col.search !== false && col.dataIndex === field.dataIndex);
});
// Optimised version
const colMap = newCols.reduce((a, c) => {
if (c.search !== false) a[c.dataIndex] = c;
return a;
}, {});
newFields.forEach((field, index) => {
const matchCol = colMap[field.dataIndex];
});JavaScript‑Specific Optimisation
Benchmarking Date.now() vs +new Date() shows a ~56% speed difference on Chrome 101.
Front‑End Optimisation
Minimise component re‑renders, especially for expensive components.
import { useState } from "react";
export default function App() {
let [color, setColor] = useState("red");
return (
<div>
<input value={color} onChange={e => setColor(e.target.value)} />
<p style={{ color }}>{"Hello, world!"}</p>
<ExpensiveTree />
</div>
);
}
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {}
return <p>I am a very slow component tree.</p>;
}By extracting the expensive component into a separate Form component, colour changes no longer trigger its re‑render.
export default function App() {
return (
<>
<Form />
<ExpensiveTree />
</>
);
}
function Form() {
let [color, setColor] = useState("red");
return (
<>
<input value={color} onChange={e => setColor(e.target.value)} />
<p style={{ color }}>{"Hello, world!"}</p>
</>
);
}Testability & Completeness
Testing Pyramid & Unit Tests
The pyramid (unit, integration, component, E2E, exploratory) shows that most effort should be spent on unit tests.
The internal "AIR" principle defines a good unit test:
A : Automatic – fully automated and non‑interactive.
I : Independent – tests must not depend on each other.
R : Repeatable – can run on every CI build.
describe('province', function() {
let asia;
beforeEach(function() {
asia = new Province(sampleProvinceData());
});
it('shortfall', function() { expect(asia.shortfall).equal(5); });
it('profit', function() { expect(asia.profit).equal(230); });
});Completeness
Tests should cover functional, boundary, and negative cases.
Design Principles
SOLID
Single‑Responsibility Principle (SRP)
Each module should have only one responsibility.
class Modem {
dial(pno) {}
hangup() {}
send(s) {}
recv() {}
}Refactored into two classes:
class Connection {
dial(pno) {}
hangup() {}
}
class DataChannel {
send(s) {}
recv() {}
}
class Modem extends Connection, DataChannel {}Open‑Closed Principle (OCP)
Modules should be open for extension but closed for modification.
function drawAllShapes(list) {
for (const item of list) {
switch (item.type) {
case ShapeType.circle: return drawCircle(item);
case ShapeType.square: drawSquare(item);
}
}
}Refactored using polymorphism and hooks:
class Circle { draw() {} }
class Square { draw() {} }
function drawAllShapes(list, hook) {
list = hook?.beforeDraw?.(list);
for (const item of list) item.draw();
list = hook?.afterDraw?.(list);
}Liskov Substitution Principle (LSP)
Subclasses must be replaceable for their base class.
class Rectangle {
setWidth(w) { this._w = w; }
setHeight(h) { this._h = h; }
getWidth() { return this._w; }
getHeight() { return this._h; }
}
class Square extends Rectangle {
setWidth(w) { super.setWidth(w); super.setHeight(w); }
setHeight(h) { super.setWidth(h); super.setHeight(h); }
}Dependency Inversion Principle (DIP)
High‑level modules should not depend on low‑level modules; both should depend on abstractions.
class Button {
private device: SwitchableDevice;
private status = false;
constructor(device: SwitchableDevice) { this.device = device; }
poll() {
this.status = !this.status;
const method = this.status ? 'turnOn' : 'turnOff';
this.device[method]();
}
}Interface Segregation Principle (ISP)
Split non‑cohesive interfaces into multiple focused ones.
interface IOrder { apply(); approve(); end(); }
interface IProductOrder extends IOrder { changeSupplier(); }
interface ISaleOrder extends IOrder { changeShop(); }
interface IProductSaleOrder extends IProductOrder, ISaleOrder { bindSupplierWithShop(); }KISS
Keep it Simple and Stupid – simple, clear designs improve maintainability and testability.
Common Code Smells
Coding Smells
Mysterious Naming
Good names convey meaning without comments.
Duplicate Code & Data Clumps
Repeated code and grouped data make maintenance painful.
Long Functions / Parameter Lists / Classes
Long code is hard to understand and test.
Dead Code
Commented‑out or unused code should be removed.
Design Smells
Scatter‑shot Changes & Shotgun Modifications
Adding a feature requires touching many unrelated files, violating OCP.
Over‑Generalisation
Prematurely adding hooks for imagined future needs creates complexity.
Insider Trading
Data exchange logic scattered across modules should be centralised.
Attachment Complex
Functions that heavily interact with another module belong there.
Excessive Delegation
When half the methods simply forward to another class, remove the middleman.
Refactoring Bad‑Smell Code
Extract Function
function printOwing(invoice) {
let outstanding = 0;
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
for (const o of invoice.orders) { outstanding += o.amount; }
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}After refactoring:
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printOwing(invoice) {
let outstanding = 0;
printBanner();
for (const o of invoice.orders) outstanding += o.amount;
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
printDetails(invoice, outstanding);
}Extract Variable
function calc(order) {
return order.quantity * order.itemPrice -
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
Math.min(order.quantity * order.itemPrice * 0.1, 100);
}Refactored:
function getQuantityDiscount(order) {
return Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
}
function calc(order) {
const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = getQuantityDiscount(order);
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
}Extract Class
class Person {
get officeAreaCode() { return this._officeAreaCode; }
get officeNumber() { return this._officeNumber; }
}After extracting TelephoneNumber:
class TelephoneNumber {
get areaCode() { return this._areaCode; }
get number() { return this._number; }
}
class Person {
get officeAreaCode() { return this._telephoneNumber.areaCode; }
get officeNumber() { return this._telephoneNumber.number; }
}Split Loop
let averageAge = 0;
let totalSalary = 0;
for (const p of people) {
averageAge += p.age;
totalSalary += p.salary;
}
averageAge = averageAge / people.length;Refactored into two separate loops for clarity.
Encapsulate Variable
let defaultOwner = { firstName: "Martin", lastName: "Fowler" };Encapsulated via getter/setter:
let defaultOwnerData = { firstName: "Martin", lastName: "Fowler" };
export function defaultOwner() { return defaultOwnerData; }
export function setDefaultOwner(arg) { defaultOwnerData = arg; }Introduce Parameter Object
function amountInvoiced(startDate, endDate) { ... }
function amountReceived(startDate, endDate) { ... }Combined into:
function amountInvoiced(aDateRange) { ... }
function amountReceived(aDateRange) { ... }Simplify Conditional Logic
Replace Algorithm
function foundPerson(people) {
for (let i = 0; i < people.length; i++) {
if (people[i] === "Don") return "Don";
if (people[i] === "John") return "John";
if (people[i] === "Kent") return "Kent";
}
return "";
}Refactored:
function foundPerson(people) {
const candidates = ["Don", "John", "Kent"];
return people.find(p => candidates.includes(p)) || '';
}Guard Clauses
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}Polymorphism over Switch
class EuropeanSwallow { get plumage() { return "average"; } }
class AfricanSwallow { get plumage() { return this.numberOfCoconuts > 2 ? "tired" : "average"; } }
class NorwegianBlueParrot { get plumage() { return this.voltage > 100 ? "scorched" : "beautiful"; } }Conclusion
Must‑Do
Never trust anyone: validate all user input and backend responses.
Ensure variables used in the view cannot cause exceptions.
Write unit tests covering functional, boundary, and negative cases.
Use meaningful, consistent naming; avoid misleading names.
Avoid duplicate code and dead code.
Avoid overly long parameter lists, functions, or classes.
Recommendations
Follow the Single‑Responsibility Principle.
Apply the Open‑Closed Principle.
Eliminate insider‑trading style data exchanges.
Remove attachment complex relationships.
Resist over‑generalising for imagined future needs.
Encapsulate exported literals.
Further Reading
《代码整洁之道》 https://book.douban.com/subject/4199741/
《重构(第 2 版)》 https://book.douban.com/subject/30468597/
《编写可读代码的艺术》 https://book.douban.com/subject/10797189/
《React 性能优化》 https://juejin.cn/post/6935584878071119885
《敏捷软件开发 : 原则、模式与实践》 https://book.douban.com/subject/1140457/
《阿里巴巴 Java 开发手册》 https://kangroo.gitee.io/ajcg/#/naming-style
《naming‑cheatsheet》 https://github.com/kettanaito/naming-cheatsheet
Alipay Experience Technology
Exploring ultimate user experience and best engineering practices
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.
