Refactoring là gì? Nghệ thuật biến Code 'Rác' thành Code 'Sạch'
Refactoring không chỉ là sửa code cho đẹp. Tìm hiểu quy trình tái cấu trúc mã nguồn an toàn, nhận diện Code Smells và trả nợ kỹ thuật hiệu quả.

Hãy tưởng tượng căn phòng của bạn sau một tuần bận rộn. Quần áo vắt trên ghế, sách vở nằm ngổn ngang, và dây sạc điện thoại thì rối tung lên. Căn phòng vẫn “hoạt động” – bạn vẫn ngủ được, vẫn tìm thấy đồ (sau một hồi bới tung lên) – nhưng nó lộn xộn, khó chịu và mất thời gian để dọn dẹp nếu bạn để lâu hơn.
Code của chúng ta cũng y hệt như vậy. Khi chạy theo deadline, chúng ta viết những dòng code “tạm bợ”, copy-paste vội vàng chỉ để tính năng chạy được. Kết quả là một mớ hỗn độn mà chúng ta hay gọi là “Spaghetti Code”. Đây là lúc Refactoring (Tái cấu trúc) cần được thực hiện.
Trong bài viết này, chúng ta sẽ đi sâu vào tư duy Refactoring, cách nhận biết khi nào code đang “kêu cứu” và các kỹ thuật thực chiến để dọn dẹp mã nguồn mà không làm hỏng tính năng.
1. Refactoring là gì? (Và những hiểu lầm tai hại)
Rất nhiều lập trình viên, thậm chí cả các Project Manager (PM), thường nhầm lẫn khái niệm này.
Refactoring là quá trình thay đổi cấu trúc bên trong của phần mềm để làm cho nó dễ hiểu hơn, dễ bảo trì hơn mà không làm thay đổi hành vi bên ngoài của nó.
Phân biệt rõ ràng:
- Refactoring: Bạn viết lại hàm
calculateTotal()cho gọn hơn, tách nhỏ nó ra, đổi tên biến cho dễ hiểu. Input vào là A, Output vẫn là B. Không có tính năng mới, không có bug mới (hy vọng thế). - Rewriting (Viết lại): Bạn xóa sạch code cũ và viết lại từ đầu bằng ngôn ngữ hoặc framework mới. Đây là đập đi xây lại, không phải Refactoring.
- Bug Fixing: Code đang chạy sai, bạn sửa cho nó chạy đúng. Đây là sửa lỗi, không phải Refactoring thuần túy.
Chân lý: Refactoring giống như việc bạn dọn dẹp nhà bếp sau khi nấu ăn. Món ăn (tính năng) đã lên mâm rồi, giờ bạn phải rửa bát, sắp xếp dao thớt để lần sau nấu nướng (phát triển tính năng mới) nhanh hơn và không bị… ngộ độc.
2. Tại sao phải Refactoring? Cái giá của “Nợ kỹ thuật”
Trong ngành phần mềm, có một thuật ngữ nổi tiếng là Technical Debt (Nợ kỹ thuật).
Khi bạn viết code ẩu để chạy cho nhanh, bạn đang “vay nợ”. Bạn vay thời gian của tương lai để dùng cho hiện tại. Nếu bạn không Refactoring để trả nợ, lãi suất sẽ tăng dần:
- Code khó đọc: Người mới vào team (hoặc chính bạn sau 6 tháng) nhìn vào code và thốt lên: “Cái quái gì đây?“.
- Bug ẩn nấp: Code rắm rối, logic lồng nhau quá nhiều tầng là nơi trú ẩn hoàn hảo cho bugs.
- Tốc độ phát triển giảm: Thêm một tính năng mới vào đống code lộn xộn mất 5 ngày, thay vì chỉ 1 ngày nếu code sạch.
3. Code Smells: Những dấu hiệu Code đang “bốc mùi”
Làm sao biết khi nào cần Refactoring? Hãy dùng mũi của bạn. Kent Beck (tác giả của Extreme Programming) đã đặt ra thuật ngữ “Code Smells” – những dấu hiệu cho thấy code có vấn đề về thiết kế.
Dưới đây là những mùi phổ biến nhất trong JavaScript/TypeScript:
3.1. Long Method (Hàm quá dài)
Một hàm chỉ nên làm một việc duy nhất (nguyên lý Single Responsibility). Nếu hàm của bạn dài hơn 20-30 dòng, hoặc bạn phải cuộn chuột mỏi tay mới hết một function, nó đang “bốc mùi”.
3.2. Duplicated Code (Lặp code)
Nguyên lý DRY (Don’t Repeat Yourself). Nếu bạn thấy mình đang copy-paste một đoạn logic từ file A sang file B và chỉ sửa lại một chút xíu -> Báo động đỏ!
3.3. Magic Numbers / Magic Strings
Sử dụng các con số hoặc chuỗi ký tự trực tiếp trong code mà không giải thích ý nghĩa.
Code “bốc mùi”:
if (user.status === 2) { // Số 2 là cái gì? // logic... }Code sạch:
JavaScript
const USER_STATUS = {
ACTIVE: 1,
INACTIVE: 2
};
if (user.status === USER_STATUS.INACTIVE) {
// logic...
}
3.4. Callback Hell (Địa ngục Callback)
Đặc sản của JavaScript cũ. Các hàm lồng nhau quá sâu tạo thành hình kim tự tháp.
Code “bốc mùi”:
JavaScript
getData(function (a) {
getMoreData(a, function (b) {
getMoreMoreData(b, function (c) {
console.log(c);
});
});
});Giải pháp: Sử dụng Promise hoặc async/await để làm phẳng code.
4. Kỹ thuật Refactoring thực chiến (Có ví dụ)
Chúng ta hãy cùng xem một ví dụ cụ thể và áp dụng các kỹ thuật Refactoring cơ bản.
Ví dụ: Tính hóa đơn tiền hàng
Giả sử ta có một đoạn code tính tổng tiền cho giỏ hàng như sau:
JavaScript
// Code gốc (Messy)
function printOwing(invoice) {
let outstanding = 0;
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
// Tính tiền
for (const o of invoice.orders) {
outstanding += o.amount;
}
// Ghi lại hạn thanh toán
const today = new Date();
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
// In ra chi tiết
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}Đoạn code trên vi phạm nhiều quy tắc: trộn lẫn việc tính toán và việc in ấn, hàm làm quá nhiều việc. Hãy áp dụng kỹ thuật Extract Method (Trích xuất hàm).
Bước 1: Tách phần in Banner
JavaScript
function printBanner() {
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
}Bước 2: Tách phần tính toán logic
JavaScript
function calculateOutstanding(orders) {
return orders.reduce((total, order) => total + order.amount, 0);
}Bước 3: Tách phần set hạn thanh toán
JavaScript
function setDueDate(invoice) {
const today = new Date();
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}Kết quả sau khi Refactor:
JavaScript
// Code sạch (Clean)
function printOwing(invoice) {
printBanner();
const outstanding = calculateOutstanding(invoice.orders);
setDueDate(invoice);
printDetails(invoice, outstanding);
}
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
Bây giờ, hàm printOwing trông giống như một mục lục (Table of Contents). Bạn đọc vào là hiểu ngay luồng xử lý mà không cần quan tâm chi tiết từng dòng for hay Date xử lý ra sao.
5. Nguyên tắc vàng để Refactoring an toàn
Refactoring rất dễ gây ra lỗi (Regression bugs) nếu bạn không cẩn thận. Hãy tuân thủ các nguyên tắc sau:
5.1. Không Refactor khi chưa có Test
Đây là quy tắc sinh tử. Làm sao bạn biết việc bạn sửa code không làm hỏng logic cũ?
- Nếu dự án chưa có Unit Test, hãy viết test cho đoạn code cũ trước (Characterization Test).
- Đảm bảo test pass (xanh) -> Refactor -> Chạy lại test -> Vẫn xanh -> Thành công.
5.2. Mũ Refactoring vs Mũ Feature
Đừng bao giờ làm hai việc cùng lúc.
- Khi đội “Mũ Feature”: Bạn chỉ tập trung làm cho tính năng chạy được (có thể code hơi xấu). Tuyệt đối không sửa code cũ.
- Khi đội “Mũ Refactoring”: Bạn chỉ sửa cấu trúc code. Tuyệt đối không thêm tính năng mới.
5.3. Quy tắc Hướng đạo sinh (The Boy Scout Rule)
Hội Hướng đạo sinh Mỹ có một quy tắc: “Luôn để lại khu cắm trại sạch sẽ hơn lúc bạn đến”.
Trong lập trình:
“Mỗi khi bạn mở một file code ra để sửa lỗi hoặc thêm tính năng, hãy dọn dẹp nó sạch hơn một chút so với lúc bạn mở nó ra.”
Bạn không cần refactor toàn bộ hệ thống trong một đêm. Chỉ cần đổi tên một biến cho rõ nghĩa, tách một hàm con… Tích tiểu thành đại, code base của bạn sẽ dần trở nên khỏe mạnh.
6. Khi nào KHÔNG nên Refactoring?
Mặc dù tôi ca ngợi Refactoring nãy giờ, nhưng có những lúc bạn nên dừng lại:
- Khi Deadline đã dí sát cổ: Refactoring cần thời gian và sự tập trung. Nếu ngày mai release, hôm nay đừng dại dột ngồi cấu trúc lại cả hệ thống Auth.
- Khi code quá tệ hại: Đôi khi code cũ nát đến mức việc sửa nó tốn nhiều công sức hơn là viết lại từ đầu. Lúc này hãy cân nhắc Rewrite (nhưng phải cực kỳ thận trọng).
Kết luận: Code sạch là một hành trình
Refactoring không phải là một task bạn làm một lần rồi xong. Nó là một thói quen, một tư duy cần được rèn luyện hàng ngày.
Một Web Developer giỏi không phải là người viết ra đoạn code chạy được nhanh nhất, mà là người viết ra đoạn code mà 6 tháng sau, đồng nghiệp (hoặc chính họ) đọc lại vẫn cảm thấy dễ chịu và dễ dàng mở rộng.
Đừng để “Nợ kỹ thuật” biến dự án của bạn thành một cơn ác mộng. Hãy bắt đầu trả nợ ngay hôm nay từ những việc nhỏ nhất.