Mã chết và mã zombie trong phát triển: định nghĩa, nguyên nhân và tìm kiếm

Tác giả: IT Sectr Đã đăng: 2026-07-26 Thời gian đọc: 10 phút

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 — đoạn không bao giờ được thực thi nhưng vẫn tồn tại trong dự án.
  • Mã zombie — mã từng được thực thi nhưng sau thay đổi trở nên không thể truy cập.
  • Mã chết làm tăng kích thước tệp nhị phân, thời gian biên dịch và tải nhận thức của nhóm.
  • Công cụ tìm kiếm chính: phân tích tĩnh (SonarQube, ESLint) và trình hồ sơ bao phủ.
  • Xóa mã chết an toàn qua kiểm tra bao phủ kiểm thử và đánh giá mã.

Mã chết là gì?

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.

Sự khác biệt giữa mã chết và mã zombie

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 xuất hiện mã chết

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.

Sự nguy hiểm của mã chế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.

Công cụ tìm kiếm mã chết

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.

Ví dụ tìm kiếm trong Kotlin qua ProGuard

groovy
// 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.

Phân tích động qua bao phủ kiểm thử

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.

Cách xóa mã chết an toà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.

cpp
// 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

Mã chết có thể gây ra lỗi biên dịch không?

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 nguy hiểm thế nào cho người mới trong nhóm?

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.

Làm thế nào để tìm mã chết trong dự án JavaScript?

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.

Có nên xóa mã chết trước khi phát hành không?

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.

Trình biên dịch có giúp tự động xóa mã chết khô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

  • Mã chết — đoạn không sử dụng không bao giờ được thực thi nhưng vẫn tồn tại trong dự án.
  • Mã zombie — phân loại mã chết từng hoạt động nhưng mất tính liên quan sau tái cấu trúc.
  • Nguyên nhân chính: phát triển lặp, feature toggle, tự động tạo và sợ xóa.
  • Mã chết làm tăng thời gian biên dịch, kích thước tệp nhị phân và tải nhận thức của nhóm.
  • Công cụ tìm kiếm: SonarQube, ESLint, SwiftLint, pylint, vulture, knip, ProGuard, JaCoCo.
  • Xóa an toàn bao gồm: tìm kiếm, phân tích git, xóa trong nhánh, chạy kiểm thử và đánh giá mã.
  • Phòng ngừa mã chết: linter trong CI, cảnh báo mã không sử dụng trong đánh giá mã và văn hóa tái cấu trúc.

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.

Thảo luận dự án

Đọc thêm