Mã nguồn kém là một thuật ngữ lóng để chỉ mã nguồn chất lượng thấp: khó đọc, cấu trúc kém và khó bảo trì. Theo báo cáo của Stripe (2022), các nhà phát triển dành tới 40% thời gian làm việc để đọc và hiểu mã được viết kém. Trong cộng đồng nói tiếng Nga, thuật ngữ này phổ biến đến mức có một trang web chuyên dụng govnokod.ru nơi các nhà phát triển đăng các ví dụ về những trường hợp đặc biệt nổi bật.
Những điểm chính
Mã nguồn kém là một đặc tính chủ quan nhưng được chấp nhận rộng rãi của mã không đáp ứng các tiêu chuẩn chất lượng tối thiểu. Robert Martin trong cuốn sách Clean Code (2008) định nghĩa mã kém là mã “cản trở việc hiểu nó làm gì”. Mã nguồn kém có thể đúng cú pháp và thậm chí hoạt động, nhưng việc bảo trì nó trở thành cơn ác mộng cho nhóm.
Thuật ngữ mã nguồn kém đặc biệt phổ biến trong cộng đồng nói tiếng Nga. Trong tiếng Anh, các thuật ngữ trang trọng hơn được sử dụng: spaghetti code, dirty code, technical debt code. Tuy nhiên, sắc thái cảm xúc của “mã nguồn kém” truyền tải chính xác hơn thái độ của các nhà phát triển đối với loại mã này — sự pha trộn giữa bực bội, ghê tởm và xúc phạm nghề nghiệp.
Theo một nghiên cứu của McKinsey (2023), các công ty có mức nợ kỹ thuật cao — và mã nguồn kém là thành phần chính của nó — tiêu tốn nhiều hơn 20–40% tài nguyên để phát triển các tính năng mới. Chất lượng mã ảnh hưởng trực tiếp đến các chỉ số kinh doanh, và đây không phải là phép ẩn dụ mà là một sự thật đã được xác nhận.
Không có chỉ số khách quan nào, nhưng có các tiêu chí thực tế: nếu một nhà phát triển mất hơn 5 phút để hiểu một hàm 20 dòng — đó là mã nguồn kém. Nếu thay đổi một dòng làm hỏng ba mô-đun không liên quan — đó là mã nguồn kém. Nếu mã không thể được bao phủ bởi các bài kiểm tra mà không viết lại hoàn toàn — đó là mã nguồn kém.
Sao chép-dán (copy-paste programming) là một trong những dấu hiệu rõ ràng nhất và dễ phát hiện nhất. Khi cùng một khối mã được lặp lại ở nhiều nơi với những thay đổi tối thiểu, đó không chỉ là mã nguồn kém — đó là nguồn gốc của các lỗi trong tương lai. Sửa ở một chỗ và bỏ sót ở chỗ khác là tình huống điển hình.
Tên biến vô nghĩa là một dấu hiệu kinh điển. Các biến có tên như `a`, `b`, `x`, `data`, `temp`, `tmp`, `result`, `list`, `obj` không mang bất kỳ thông tin nào về mục đích của chúng. Người đọc mã phải phân tích toàn bộ hàm để hiểu biến đó chứa gì. Robert Martin gọi đây là “lời nói dối trong tên” — cái tên hứa hẹn thông tin nhưng không cung cấp nó.
Lồng ghép sâu — khi các điều kiện, vòng lặp và xử lý lỗi tạo ra một cấu trúc với 5+ cấp độ thụt lề. Mã như vậy không thể đọc được nếu không cuộn ngang hoặc theo dõi tinh thần tất cả các cấp độ. Đây là con đường trực tiếp dẫn đến lỗi: các toán tử logic dễ bị nhầm lẫn và dấu ngoặc đóng dễ bị bỏ sót.
| Dấu hiệu | Ví dụ mã nguồn kém | Mã sạch |
|---|---|---|
| Sao chép-dán | Một khối được sao chép 5 lần | Được trích xuất thành một hàm |
| Tên | `var a = getData()` | `var userList = getData()` |
| Lồng ghép | 6 cấp độ if/for | 2–3 cấp độ với return early |
| Hàm | Hàm 300 dòng | Được chia thành 3–5 phương thức |
| Chú thích | `i++ // tăng i` | Mã tự giải thích không cần chú thích |
Mã chết (dead code) — các hàm, biến, lớp không được sử dụng ở đâu cả. Điều này làm tăng khối lượng mã, làm xao nhãng nhà phát triển và tạo ấn tượng sai về khả năng của hệ thống. Số ma thuật — các con số không có ngữ cảnh. Lớp God — các lớp làm mọi thứ cùng một lúc, vi phạm nguyên tắc đơn trách nhiệm (SOLID: S).
Thiếu thời gian là nguyên nhân phổ biến nhất. Khi thời hạn gần kề, các nhà phát triển hy sinh chất lượng để lấy tốc độ. Về mặt chiến thuật, điều này có thể được biện minh, nhưng về mặt chiến lược — đó là tích lũy nợ kỹ thuật. Vấn đề là mã nguồn kém “tạm thời” hiếm khi được xem xét lại để sửa chữa.
Thiếu đánh giá mã là nguyên nhân quan trọng thứ hai. Khi mã được viết một mình mà không có sự đánh giá của đồng nghiệp, các mô hình xấu sẽ bám rễ và nhân lên. Đánh giá mã không chỉ là kiểm soát chất lượng mà còn là chuyển giao kiến thức trong nhóm. Các dự án không có đánh giá chắc chắn sẽ suy thoái thành mã nguồn kém.
Trình độ nhà phát triển thấp hoặc thiếu người hướng dẫn. Các nhà phát triển junior bị bỏ mặc mà không có giám sát tự nhiên sẽ viết mã nguồn kém — đó là một phần của quá trình học tập. Vấn đề phát sinh khi mã này được đưa vào sản xuất mà không có đánh giá và tái cấu trúc.
Trong các nhóm mà phương châm là “nó chạy được là tốt rồi”, mã nguồn kém phát triển mạnh. Sự vắng mặt của các tiêu chuẩn mã hóa, yêu cầu kiểm thử và quy trình đánh giá tạo ra một môi trường nơi chất lượng mã không phải là mối quan tâm của ai. Các dự án như vậy nhanh chóng trở thành “legacy” — mã mà mọi người đều sợ động vào.
Hậu quả chính của mã nguồn kém là làm chậm quá trình phát triển. Nghịch lý của mã kém là nó cho phép viết phiên bản đầu tiên một cách nhanh chóng, nhưng mỗi lần sửa chữa tiếp theo lại tốn nhiều thời gian hơn. Biểu đồ tốc độ phát triển so với chất lượng mã là hàm mũ — sau một ngưỡng nhất định, việc thêm các tính năng mới trở nên gần như không thể.
Tỷ lệ nghỉ việc là một hậu quả gián tiếp nhưng nghiêm trọng. Các nhà phát triển, đặc biệt là những người có kinh nghiệm, không muốn làm việc với mã nguồn kém. Theo Khảo sát nhà phát triển Stack Overflow 2024, 47% nhà phát triển coi chất lượng cơ sở mã là một trong những yếu tố chính khi chọn nơi làm việc. Các dự án có mã kém sẽ mất đi những nhân viên tốt nhất.
Bảo mật là một nạn nhân khác của mã nguồn kém. Mã được viết kém chứa nhiều lỗ hổng hơn: ngoại lệ không được xử lý, SQL injection, XSS, rò rỉ bộ nhớ. Mã chất lượng với kiểm thử đơn vị và đánh giá mã sẽ bắt được hầu hết các vấn đề này trước khi đưa vào sản xuất.
SonarQube và các công cụ tương tự có thể ước tính nợ kỹ thuật bằng giờ-công hoặc ngày-công. Ví dụ, 500 cảnh báo về sao chép-dán, 200 về số ma thuật và 50 về lồng ghép sâu cho ước tính 30 ngày nợ kỹ thuật. Những con số này có thể và nên được trình bày với ban quản lý để biện minh cho việc tái cấu trúc.
Nguyên tắc DRY (Don’t Repeat Yourself) là điều đầu tiên cần thực hiện. Mọi phần logic nên tồn tại ở một nơi duy nhất. Thay vì sao chép-dán — hãy trích xuất mã lặp lại vào một hàm, lớp hoặc mô-đun riêng biệt. Thay vì số ma thuật — hãy dùng hằng số có tên. Thay vì các hàm dài — hãy chia thành nhiều hàm nhỏ.
Nguyên tắc KISS (Keep It Simple, Stupid) bảo vệ khỏi sự phức tạp quá mức. Nếu một nhiệm vụ có thể được giải quyết trong 10 dòng — đừng viết 50. Nếu một vòng lặp đơn giản hơn một stream — hãy dùng vòng lặp. Nếu một hàm thông thường rõ ràng hơn một decorator — hãy viết hàm. Đơn giản là phẩm chất chính của mã có thể bảo trì.
Quy tắc Boy Scout — “hãy để lại mã tốt hơn khi bạn tìm thấy nó.” Ngay cả những cải tiến nhỏ trong mỗi lần chỉnh sửa cũng dần dần biến mã nguồn kém thành mã tốt. Đổi tên một biến, chia một hàm lớn, thêm một bài kiểm tra — bất kỳ cải tiến nào cũng có giá trị.
// mã nguồn kém — sao chép-dán, số ma thuật, tên kém
function calc(a, b, c) {
let x = a * 0.85;
if (b > 1000) { x = x * 0.9; }
let y = c * 0.85;
if (b > 1000) { y = y * 0.9; }
return x + y;
}
// mã sạch — tên rõ ràng, DRY, hằng số
const DISCOUNT_RATE = 0.85;
const BULK_THRESHOLD = 1000;
const BULK_DISCOUNT = 0.9;
function applyDiscount(amount, quantity) {
let price = amount * DISCOUNT_RATE;
if (quantity > BULK_THRESHOLD) {
price = price * BULK_DISCOUNT;
}
return price;
}
function calculateTotal(items, quantity) {
return items.reduce((sum, item) => {
return sum + applyDiscount(item, quantity);
}, 0);
}
Hãy xem xét một ví dụ điển hình bằng Python. Hàm xử lý đơn hàng nhưng làm rất kém: 80 dòng, lồng ghép sâu, số ma thuật, trùng lặp. Sau khi tái cấu trúc, mã trở nên dễ đọc, dễ kiểm tra và dễ bảo trì.
# mã nguồn kém — một hàm làm mọi thứ
def process_order(order):
if order.get("type") == "premium":
if order["amount"] > 100:
discount = 0.8
else:
discount = 0.9
else:
discount = 1.0
total = order["amount"] * discount
return total
# mã sạch — các hàm và hằng số được trích xuất
class OrderProcessor:
PREMIUM_DISCOUNT_HIGH = 0.8
PREMIUM_DISCOUNT_LOW = 0.9
PREMIUM_THRESHOLD = 100
def get_discount(self, order):
if order.type == "premium" and order.amount > self.PREMIUM_THRESHOLD:
return self.PREMIUM_DISCOUNT_HIGH
return self.PREMIUM_DISCOUNT_LOW
def calculate_total(self, order):
return order.amount * self.get_discount(order)
Một hàm tốt làm một việc và làm tốt việc đó. Nếu một hàm làm ba việc khác nhau — hãy chia nó ra. Nếu một hàm có hơn 20 dòng — có thể nó nên được chia nhỏ. Nếu một hàm có hơn hai cấp độ thụt lề — nó cần được tái cấu trúc.
Các công cụ phân tích mã tĩnh là tuyến phòng thủ đầu tiên chống lại mã nguồn kém. ESLint (JavaScript), Pylint (Python), SonarQube (đa ngôn ngữ), Checkstyle (Java) tự động phát hiện sao chép-dán, số ma thuật, khối catch rỗng, hàm quá dài và hàng trăm phản mẫu khác.
Phong cách mã và trình định dạng là cấp độ bảo vệ thứ hai. Prettier, Black, gofmt tự động định dạng mã, loại bỏ các vấn đề về khoảng trắng, thụt lề và dấu ngoặc. Một phong cách nhất quán trong nhóm làm cho mã dễ đọc bất kể ai đã viết nó. Các tranh luận về định dạng nên được tự động hóa.
Đánh giá mã là cấp độ thứ ba và quan trọng nhất. Không một công cụ phân tích nào có thể thay thế một con người nhận thấy kiến trúc giải pháp sai hoặc nhà phát triển đã chọn cách tiếp cận sai. Đánh giá hiệu quả cần thời gian, nhưng nó được đền đáp bằng cách giảm đáng kể lượng mã nguồn kém.
Các câu hỏi thường gặp
Cực kỳ hiếm. Trong tạo mẫu thử hoặc hackathon, tốc độ quan trọng hơn chất lượng, nhưng mã như vậy nên được đánh dấu là tạm thời và không được đưa vào sản xuất mà không tái cấu trúc. Trong sản xuất, không có lý do gì cho mã nguồn kém — bất kỳ thời gian tiết kiệm được bây giờ sẽ biến thành tổn thất nhân lên trong tương lai.
Mã của người mới là mã thiếu kinh nghiệm nhưng thường chân thành và sẽ cải thiện khi kỹ năng phát triển. Mã nguồn kém là sự coi thường chất lượng có ý thức hoặc thờ ơ. Người mới có thể viết mã chưa tối ưu nhưng có thể đọc được. Mã nguồn kém thì ngược lại, về cơ bản là không thể đọc được — tác giả của nó không quan tâm liệu người khác có hiểu nó không.
Viết lại là biện pháp cuối cùng. Tái cấu trúc dần dần an toàn hơn: bạn cô lập một mô-đun, bao phủ nó bằng các bài kiểm tra, viết lại từng phần. Viết lại hoàn toàn rất rủi ro — bạn có thể mất logic kinh doanh tích lũy trong mã cũ, bao gồm cả xử lý các trường hợp biên mà không ai ghi chép lại.
Hãy sử dụng các chỉ số: SonarQube sẽ hiển thị nợ kỹ thuật theo giờ. Hãy cho thấy bao nhiêu thời gian bị lãng phí cho lỗi trong mã cũ. So sánh tốc độ phát triển tính năng mới trong phần “sạch” và “bẩn” của dự án. Dịch sang ngôn ngữ kinh doanh: thời gian là tiền bạc, và mã nguồn kém tốn tiền.
Clean Code của Robert Martin (2008) là kinh thánh của lập trình chất lượng. Nó bao gồm các nguyên tắc đặt tên, định dạng, xử lý lỗi và kiểm thử. Bổ sung: Code Complete của Steve McConnell, Refactoring của Martin Fowler, Design Patterns của Gang of Four. Mọi nhà phát triển nên đọc những cuốn sách này.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm