Code Quality and Refactoring
Preface
Is it enough that your code just works? You may have written code like this: the feature works, but two weeks later even you can't understand it. Or someone on the team left behind a pile of "code that only God and they can understand."
This chapter helps you understand what good code is, how to identify bad code, and how to safely improve it.
What will you learn in this article?
| Chapter | Content | Core Concepts |
|---|---|---|
| Chapter 1 | Code Smells | Identifying common problems |
| Chapter 2 | Refactoring Techniques | Safely improving code |
| Chapter 3 | Code Review | Quality assurance in team collaboration |
| Chapter 4 | Quality Metrics | Measuring code health with data |
After reading this chapter, you will be able to identify code problems, refactor safely, and continuously improve code quality through team collaboration.
0. The Big Picture: The Lifecycle of Code
In software development, there is an often-overlooked fact: code is read far more times than it is written.
A piece of code, from creation to retirement, roughly goes through this journey:
The Life of Code
- Writing Phase: A developer writes the first implementation. The feature works, tests pass.
- Review Phase: Team members read the code and suggest improvements.
- Maintenance Phase: Fixing bugs, adding features, adapting to new requirements — this phase accounts for over 80% of the code's lifecycle.
- Refactoring Phase: When code becomes hard to maintain, the internal structure needs to be improved without changing external behavior.
- Retirement Phase: Technology evolves, and old code is replaced by new solutions.
Martin Fowler once said in Refactoring: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
1. Code Smells: Identifying Common Problems
1.1 What Are Code Smells?
The concept of "Code Smell" was proposed by Kent Beck. It refers to characteristics in code that are not bugs but suggest deeper design problems. It's like a strange odor in a room — it won't make you sick immediately, but it signals that something needs cleaning.
Use the interactive component below to identify some of the most common code smells:
function processOrder(order) {
// Validate order... (20 lines)
// Calculate price... (15 lines)
// Check inventory... (10 lines)
// Send notification... (15 lines)
// Update database... (10 lines)
// Generate report... (10 lines)
// 80+ lines in total!
}📏 Long function
A function exceeds 50 lines, does too many things, and becomes hard to understand and test.
1.2 Common Code Smells Checklist
| Code Smell | Symptom | Harm |
|---|---|---|
| Long Method | Function exceeds 50 lines | Hard to understand, test, and reuse |
| Magic Numbers | Writing 86400000 directly in code | Unclear meaning, easy to miss when modifying |
| Duplicated Code | Similar logic in multiple places | Must sync changes everywhere, easy to miss |
| Deep Nesting | More than 3 levels of if/for | Logic like a maze, hard to follow |
| Long Parameter List | Function has more than 4 parameters | Difficult to call, easy to mix up order |
| God Class | One class/module does too much | Unclear responsibilities, change one thing and everything breaks |
Key Insight
Code smells are not "errors" — they are "signals." They tell you: the design here may need improvement. Not all smells need to be fixed immediately, but you need the ability to recognize them.
2. Refactoring Techniques: Safely Improving Code
2.1 What Is Refactoring?
The definition of Refactoring is precise: improving the internal structure of code without changing its external behavior.
The key phrase is "without changing external behavior." Refactoring is not rewriting, not adding features, not fixing bugs. It is the "organizing and tidying up" of code internals.
Use the component below to compare the before and after of common refactoring techniques:
function printReport(invoice) { console.log("=== Invoice ===") // Calculate total let total = 0 for (let item of invoice.items) { total += item.price * item.qty } console.log(`Total: ${total}`) }
function printReport(invoice) { console.log("=== Invoice ===") const total = calcTotal(invoice.items) console.log(`Total: ${total}`) } function calcTotal(items) { return items.reduce( (s, i) => s + i.price * i.qty, 0 ) }
2.2 Common Refactoring Techniques
Extract Function
This is the most commonly used refactoring technique. When a piece of code can be summarized with a meaningful name, it should be extracted into a function.
// Before refactoring
function printReport(data) {
// Calculate total price
let total = 0
for (const item of data.items) {
total += item.price * item.qty
}
// Print...
}
// After refactoring
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0)
}
function printReport(data) {
const total = calculateTotal(data.items)
// Print...
}Rename
Good naming is the cheapest and most effective documentation. When you need to write a comment to explain what a variable or function means, its name is not good enough.
// Before refactoring
const d = new Date() - startTime // Elapsed time
const arr = users.filter(u => u.a) // Active users
// After refactoring
const elapsedMs = new Date() - startTime
const activeUsers = users.filter(user => user.isActive)Replace Nested Conditional with Guard Clauses
// Before refactoring
function getPayAmount(employee) {
if (employee.isSeparated) {
return { amount: 0 }
} else {
if (employee.isRetired) {
return { amount: employee.pension }
} else {
return { amount: employee.salary }
}
}
}
// After refactoring
function getPayAmount(employee) {
if (employee.isSeparated) return { amount: 0 }
if (employee.isRetired) return { amount: employee.pension }
return { amount: employee.salary }
}The Safety Net for Refactoring
The biggest risk of refactoring is "introducing bugs while making changes." So the prerequisite for refactoring is having test coverage. Run tests after each small refactoring step to ensure behavior hasn't changed. For code without tests, add tests first before refactoring.
3. Code Review: Quality Assurance in Team Collaboration
3.1 Why Code Review?
Code Review is one of the most effective quality assurance methods in a team. Its value goes beyond finding bugs:
- Knowledge Sharing: Team members learn each other's code, reducing the "bus factor" (if someone gets hit by a bus, can the project continue?)
- Consistent Style: Reviews gradually establish the team's coding standards
- Early Detection of Design Issues: Bad architectural decisions are harder to fix than bugs
- Mutual Learning: Reading others' code is a shortcut to improving your own programming skills
3.2 What to Review?
| Dimension | Focus |
|---|---|
| Correctness | Is the logic correct? Are edge cases handled? |
| Readability | Are names clear? Is the structure easy to understand? |
| Security | Are there injection risks? Is sensitive data exposed? |
| Performance | Are there obvious performance issues? N+1 queries? |
| Testing | Are there corresponding tests? Do they cover critical paths? |
3.3 Review Etiquette
Good code review is a discussion about code, not criticism of people:
- Use "we" instead of "you":
"You wrote this wrong"→ "Here we could consider using a guard clause" - Ask rather than command:
"Change to const"→ "Will this variable be reassigned later? If not, const would be safer" - Give reasons: Don't just say "not good" — explain "why it's not good" and "how to make it better"
4. Code Quality Metrics
4.1 Cyclomatic Complexity
Cyclomatic Complexity measures the number of independent paths in code. Each if, for, case, &&, || increases complexity.
| Complexity | Rating | Recommendation |
|---|---|---|
| 1-10 | Simple | Easy to understand and test |
| 11-20 | Moderate | Consider splitting |
| 21-50 | Complex | Must refactor |
| 50+ | Unmaintainable | Urgent refactoring needed |
4.2 Code Coverage
Code coverage measures what proportion of code is executed by tests. Common metrics:
- Line Coverage: Proportion of executed code lines to total lines
- Branch Coverage: Proportion of executed conditional branches to total branches
The Coverage Trap
80% coverage does not mean good code quality. Coverage only tells you "which code hasn't been tested," not "whether the tests are meaningful." A test that only asserts expect(true).toBe(true) can increase coverage but is completely worthless.
4.3 Practical Tools
| Tool | Purpose |
|---|---|
| ESLint | JavaScript/TypeScript static analysis |
| Prettier | Code formatting, consistent style |
| SonarQube | Comprehensive code quality platform |
| Husky | Git hooks, automatic checks before commits |
5. AI-Powered: Using LLMs to Improve Code Quality
LLMs are already very practical in the code quality domain — they can serve as your "24/7 online code reviewer."
5.1 Identifying Code Smells
Prompt:
Please review the following code and identify code smells, including but not limited to: long methods, magic numbers, duplicated code, deep nesting, long parameter lists. For each issue, provide the specific location, description, and improvement suggestions. [Paste your code]
5.2 Automated Refactoring
Prompt:
Please refactor the following code with these requirements: 1. Do not change external behavior 2. Use techniques like extract function, guard clauses to replace nesting 3. Improve naming, eliminate magic numbers 4. Explain the reasoning behind each refactoring step [Paste your code]
5.3 Simulated Code Review
Prompt:
Please review this code from the perspective of a senior developer, providing feedback on these dimensions: - Correctness: Are there logic bugs? Are edge cases handled? - Readability: Are names clear? Is the structure easy to understand? - Performance: Are there obvious performance issues? - Security: Are there injection or data leakage risks? Use a "suggestion" tone rather than "command," and provide improvement plans. [Paste your code]
AI Usage Advice
You need to verify AI's refactoring suggestions yourself — run tests to confirm behavior hasn't changed. Treat AI as a "colleague who gives suggestions," not an "authority to trust unconditionally."
6. Summary
Looking back, we've gone from identifying problems to solving them, building a complete code quality improvement system:
- Identify: Learn to smell code smells, know where improvements are needed
- Refactor: Master safe refactoring techniques, improve in small steps under test protection
- Collaborate: Use code review to let the team collectively safeguard code quality
- Measure: Track code health with objective metrics
Final Thought
Code quality is not a one-time effort but a continuous habit. It's like keeping a room tidy — you don't wait until it's a mess to do a deep clean; you tidy up a little every day. The Boy Scout Rule says it well: leave the code cleaner than you found it.
Further Reading
- Classic Book: Martin Fowler's Refactoring: Improving the Design of Existing Code is the bible of this field.
- Clean Code: Robert C. Martin's Clean Code provides many practical coding principles.
- Practical Tools: Try configuring ESLint + Prettier + Husky in your project to experience automated code quality assurance.
- Code Review: Google's Code Review guidelines are the industry gold standard and worth studying.