Mã chết là những đoạn chương trình không bao giờ được thực thi và không ảnh hưởng đến kết quả, nhưng vẫn tồn tại vật lý trong tập tin nguồn của dự án. Không giống như các phần bị comment, mã chết được biên dịch và đưa vào tệp nhị phân, làm tăng kích thước và gây khó khăn cho việc điều hướng. Theo nghiên cứu của TIOBE Index (2025), một dự án thương mại trung bình chứa từ 10 đến 25 phần trăm mã không bao giờ được gọi. Mã zombie là một phân loại của mã chết từng hoạt động trong quá khứ, nhưng sau khi tái cấu trúc đã mất đi tính liên quan và giờ chỉ chiếm không gian. Việc dọn dẹp thường xuyên các đoạn này giúp giảm tải nhận thức cho các nhà phát triển và giảm nguy cơ lỗi khi thực hiện thay đổi.
Chính
Mã chết (dead code) là mã nguồn được bao gồm trong chương trình nhưng không bao giờ được thực thi trong bất kỳ kịch bản sử dụng nào. Trình biên dịch hoặc thông dịch xử lý nó, nhưng trong thời gian chạy, điều khiển không bao giờ đến được các phần này.
Các ví dụ kinh điển về mã chết: biến được gán giá trị nhưng không bao giờ được đọc; hàm hoặc phương thức không được gọi ở bất kỳ đâu; nhánh điều kiện không bao giờ trở thành đúng (if(false)); vòng lặp có thân không được thực thi dù chỉ một lần.
Theo báo cáo SonarQube State of Code Quality (2025), khoảng 15 phần trăm tất cả các cảnh báo trong dự án Java thương mại liên quan đến các phương thức và trường private không được sử dụng. Trong các dự án JavaScript, tỷ lệ mã không sử dụng có thể lên tới 30 phần trăm do tính chất động của ngôn ngữ và sự phong phú của các thư viện bên thứ ba.
Thường xuyên kiểm tra dự án của bạn để phát hiện mã chết — đặc biệt sau khi tái cấu trúc lớn và xóa tính năng. Một import bị quên hoặc hàm không được sử dụng hôm nay có thể biến thành mã zombie vào ngày mai, gây nhầm lẫn cho các thành viên mới trong nhóm.
Mã zombie (zombie code) là trường hợp đặc biệt của mã chết được phân biệt bởi bối cảnh lịch sử. Mã zombie đã từng hoạt động, nhưng sau những thay đổi trong hệ thống đã trở nên không thể truy cập, tuy nhiên nó không bị xóa mà được giữ lại «để phòng khi».
Sự khác biệt giữa mã chết và mã zombie nằm ở nguồn gốc. Mã chết có thể được viết sai (chưa bao giờ hoạt động), trong khi mã zombie là mã sống trước đây đã mất đi tính liên quan sau khi tái cấu trúc. Ví dụ, một hàm tính chiết khấu theo logic kinh doanh cũ đã được thay thế bằng logic mới, nhưng phương thức cũ không bị xóa — phòng trường hợp cần quay lại.
Nguy hiểm chính của mã zombie là ảo tưởng về chức năng đang hoạt động. Một nhà phát triển mới thấy một hàm, đọc tài liệu của nó, cho rằng nó được gọi ở đâu đó — và lãng phí thời gian nghiên cứu một hiện vật. Khi cố gắng gọi trực tiếp, có thể phát hiện ra nó phụ thuộc vào các thực thể đã bị xóa hoặc API cũ.
Theo dõi mã zombie qua lịch sử git: nếu một hàm không được thay đổi trong hai năm và không được sử dụng — đó là zombie. Hãy xóa nó không do dự, vì git giữ lịch sử và mã luôn có thể được khôi phục nếu cần.
Nguyên nhân đầu tiên và phổ biến nhất — phát triển lặp với tái cấu trúc không hoàn chỉnh. Nhóm thêm chức năng mới thay thế chức năng cũ nhưng không xóa các mô-đun đã được thay thế. Các sprint tích lũy những «cái đuôi» này, và sau một năm, dự án bị phủ một lớp mã chết.
Nguyên nhân thứ hai — kiểm thử A/B và feature toggle. Các điều kiện kích hoạt tính năng mới có thể trở nên cố định theo thời gian (ví dụ: luôn true), nhưng nhánh else với logic thay thế vẫn còn trong mã. Các nhà phát triển sợ xóa nó đề phòng vô tình làm hỏng hệ thống nếu toggle được bật lại.
Nguyên nhân thứ ba — tự động tạo và sao chép-dán. Trình tạo mã (IDE, công cụ template) tạo các khung với phương thức mà nhà phát triển không điền hoặc không sử dụng. Mã được sao chép từ dự án khác thường chứa toàn bộ khối không liên quan đến ngữ cảnh mới.
Nguyên nhân thứ tư — sợ xóa. Trong các dự án lớn, nhà phát triển sợ xóa mã vì họ không chắc chắn rằng nó thực sự không được sử dụng ở đâu. Nỗi sợ này càng trầm trọng hơn do hệ thống kiểm thử yếu: nếu không có kiểm tra tự động, việc xóa có thể dẫn đến lỗi chỉ được phát hiện trong môi trường sản xuất.
Mã chết ảnh hưởng trực tiếp đến bốn khía cạnh chất lượng dự án: hiệu suất biên dịch, kích thước tạo phẩm, tải nhận thức của nhóm và độ tin cậy của tái cấu trúc.
Tăng thời gian biên dịch: trình biên dịch xử lý các tệp không sử dụng, phân tích phụ thuộc và tạo bytecode hoặc mã máy cho các đoạn sẽ không bao giờ chạy. Trong các dự án lớn, điều này thêm phút vào mỗi lần biên dịch. Đối với ngôn ngữ thông dịch (JavaScript, Python), thời gian tải mô-đun và tiêu thụ bộ nhớ tăng lên.
Rủi ro lỗi khi sửa đổi: nhà phát triển thay đổi mã và không nghi ngờ rằng hàm chỉ được sử dụng trong nhánh chết. Sau khi tái cấu trúc, mã chết ngừng biên dịch hoặc tạo ra lỗi — nhóm lãng phí thời gian chẩn đoán vấn đề không ảnh hưởng đến hoạt động của ứng dụng.
Tải nhận thức — yếu tố đắt giá nhất. Mỗi hàm không sử dụng đòi hỏi sự chú ý khi đọc mã. Nhà phát triển tiêu tốn năng lượng tinh thần để hiểu tại sao mã này tồn tại và nó được gọi ở đâu. Nghiên cứu của Developer Productivity Lab (2025) cho thấy: xóa 20 phần trăm mã chết giảm thời gian hội nhập trung bình 18 phần trăm.
Xóa mã chết ngay khi phát hiện. Mỗi ngày chậm trễ làm tăng khả năng ai đó trong nhóm sẽ lãng phí hàng giờ nghiên cứu một hiện vật đáng lẽ phải bị xóa từ hôm qua.
Việc tìm kiếm mã chết được thực hiện bằng hai phương pháp chính: phân tích tĩnh (không chạy chương trình) và phân tích động (hồ sơ bao phủ trong thời gian chạy). Mỗi cách tiếp cận hiệu quả cho các loại mã chết khác nhau.
Trình phân tích tĩnh hỗ trợ tất cả các ngôn ngữ lập trình phổ biến. Cho Java và Kotlin — SonarQube, IntelliJ IDEA Inspections, SpotBugs. Cho JavaScript và TypeScript — ESLint với quy tắc no-unused-vars và no-unused-modules. Cho Swift — SwiftLint với quy tắc unused_declaration. Cho Python — pylint với tùy chọn unused-import và vulture để tìm kiếm sâu.
// build.gradle.kts - cấu hình ProGuard cho Android
android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
// proguard-rules.pro - chỉ giữ các lớp cần thiết
-keep class com.example.app.** { *; }
-assumenosideeffects class Timber {
static <methods>;
}
ProGuard không chỉ xóa các lớp và phương thức không sử dụng mà còn thu nhỏ tên trong bản dựng phát hành. Bản dựng với ProGuard được kích hoạt tự động hiển thị lớp và phương thức nào được coi là không sử dụng — trong báo cáo usage.txt liệt kê tất cả mã đã xóa.
Công cụ bao phủ mã (JaCoCo cho Java, XCTest coverage cho Swift, Istanbul cho JavaScript) hiển thị dòng và nhánh nào được thực thi trong quá trình kiểm thử. Các phương thức có bao phủ bằng không — ứng viên cho mã chết. Tuy nhiên, việc thiếu bao phủ không đảm bảo mã không được gọi trong sản xuất — để chắc chắn hoàn toàn, hãy sử dụng kết hợp phân tích tĩnh và động.
Thiết lập đường ống CI của bạn để bản dựng thất bại khi vượt quá ngưỡng khai báo không sử dụng. Cổng chất lượng SonarQube với quy tắc «ỷ lệ mã private không sử dụng không quá 3%» ngăn chặn sự tích tụ mã chết ở cấp độ quy trình phát triển.
Quy trình xóa mã chết bao gồm bốn bước: tìm, kiểm tra, xóa, kiểm tra lại. Bỏ qua bất kỳ bước nào làm tăng nguy cơ hồi quy.
Bước đầu tiên — tìm kiếm ứng viên qua trình phân tích tĩnh. Nhận báo cáo về các khai báo không sử dụng: hàm, lớp, biến, import. Lọc các dương tính giả — trình phân tích đôi khi sai trong trường hợp phản chiếu, tải lớp động hoặc các lệnh gọi ẩn qua tuần tự hóa.
Bước thứ hai — kiểm tra qua git blame và lịch sử thay đổi. Xem mã được viết khi nào và tại sao. Nếu mã là một phần của tính năng bị tắt bởi feature toggle — đảm bảo toggle đã cố định và sẽ không được bật lại. Comment mã bạn do dự xóa và để lại TODO với ticket để kiểm tra lại sau một tháng.
Bước thứ ba — xóa trong nhánh riêng với việc chạy đầy đủ bộ kiểm thử. Nếu kiểm thử qua — khả năng hồi quy thấp. Nếu kiểm thử thất bại — mã vẫn còn được sử dụng và cần hiểu trong kịch bản nào.
// before - mã chết và mã zombie trong cùng tệp
int calculateV1(int price) { // không được gọi ở bất kỳ đâu
int tax = price * 0.18;
return price + tax;
}
int calculateV2(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
// after - mã chết đã xóa, mã zombie đã dọn
int calculatePrice(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
Bước thứ tư — đánh giá mã các thay đổi. Người đánh giá phải xác nhận rằng mã thực sự chết. Nếu người đánh giá không chắc chắn — để lại comment trong mã và hoãn xóa cho đến khi phân tích hoàn chỉnh. Sau khi hợp nhất nhánh — xóa nhánh để tránh sinh sôi mã zombie trong kho git.
Áp dụng quy tắc: không có yêu cầu kéo nào được chứa mã chết mới. Thêm trình linter vào hook pre-commit chặn commit khi có biến hoặc import không sử dụng. Phòng ngừa luôn rẻ hơn dọn dẹp.
Câu hỏi thường gặp
Có, nếu mã chết chứa lỗi cú pháp hoặc tham chiếu đến các kiểu đã xóa. Trình biên dịch hiện đại vẫn kiểm tra các nhánh chết, do đó lỗi trong khối if(false) sẽ gây ra lỗi biên dịch. Đây là sự bảo vệ: mã không nên chết đến mức trình biên dịch không kiểm tra nó.
Mã zombie gây hiểu lầm: một nhà phát triển mới thấy hàm có tài liệu và cho rằng nó được sử dụng. Anh ta lãng phí thời gian nghiên cứu mã không hoạt động và có thể vô tình buộc logic mới vào thực thể lỗi thời, tạo ra lỗi khó truy tìm.
Sử dụng ESLint với quy tắc no-unused-vars và no-unused-modules, cùng với tiện ích knip — nó phân tích exports và imports trên toàn bộ dự án, tìm các tệp, hàm và phụ thuộc không sử dụng. Đối với kho đơn lớn, knip cho thấy bức tranh đầy đủ nhất.
Tốt nhất nên xóa trước khi phát hành, nhưng không phải vào phút cuối. Xóa mã chết là công việc kỹ thuật được lên kế hoạch riêng trong sprint. Ngay trước khi phát hành, việc xóa có thể gây mất ổn định nếu mã không chết như tưởng tượng.
Có, trình biên dịch hiện đại và trình nén tối thiểu (ProGuard, R8, Terser, Closure Compiler) xóa mã không thể truy cập ở cấp độ Dead Code Elimination. Tuy nhiên, điều này không loại bỏ nhu cầu dọn dẹp nguồn: trình biên dịch xóa mã khỏi tệp nhị phân, nhưng không xóa khỏi kho — nhà phát triển vẫn vấp phải nó khi đọc.
Tổng kế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