In software development, dead code is a piece of code that is never executed. There are many software tools that can help us identify dead code in the codebase.
We have often been told that dead code can be very dangerous and should be removed from the codebase. However, for a very long time, I actually did not understand why dead code should be removed. Since it’s never executed, just let it be and maybe later I can reuse it.
In this blog post, I will discuss why dead code is dangerous and should be removed from the codebase by orchestrating a simple example.
Dead Code Is Dangerous
In this example, I created a program for a million-dollar business. If this program does not work correctly, the business can lose a lot of money.
This program is bug-free at runtime before the upgrade. In the presence of dead code, after the upgrade, the program becomes problematic at runtime because the buggy dead code becomes reachable.
std::string std_string_centered(std::string const& s, size_t width, char pad = ' ') { size_tconst l{s.length()}; // Throw an exception if width is too small. if (width < l) { throw std::runtime_error("Width is too small."); } size_tconst left_pad{(width - l) / 2}; size_tconst right_pad{width - l - left_pad}; std::string const s_centered{std::string(left_pad, pad) + s + std::string(right_pad, pad)}; return s_centered; }
#if defined(ORIGINAL) enum classColor { RED = 0, GREEN = 1, COUNT = 2 }; #elif defined(UPGRADED) // A sloppy program upgrade. // The developer forgot to add a new unit test for the new color. // The reviewer approved this change because this change did not break the // existing unit tests. enum classColor { RED = 0, GREEN = 1, BLUE = 2, COUNT = 3 }; #else static_assert(false); #endif
// This function is the user interface function which cannot always be tested // rigorously. Color get_color_from_user() { // We simulate the user behavior by always returning the last color defined // in Color. returnstatic_cast<Color>(static_cast<int>(Color::COUNT) - 1); }
voidwork_on_color_red() { std::cout << "Doing some million-dollar business on color red..." << std::endl; }
voidwork_on_color_green() { std::cout << "Doing some million-dollar business on color green..." << std::endl; }
#if !defined(REMOVE_DEAD_CODE) voidwork_on_color_others() { std::cout << "Doing some million-dollar business on color others..." << std::endl; // We accidentally introduced a severe bug in this function. std::terminate(); } #endif
voidwork_on_color(Color color) { switch (color) { case Color::COUNT: // Do nothing. break; case Color::RED: work_on_color_red(); break; case Color::GREEN: work_on_color_green(); break; #if !defined(REMOVE_DEAD_CODE) default: // This is a piece of dead code which will never be executed before // program upgrade. The severe bug in work_on_color_others() will // never be triggered before program upgrade. work_on_color_others(); break; #endif } }
voidunit_test() { work_on_color(Color::COUNT); // Iterate through all the colors. work_on_color(Color::RED); work_on_color(Color::GREEN); }
intmain() { size_tconst width{55}; // Our program should work fine by unit test. std::cout << std_string_centered("", width, '*') << std::endl; std::cout << std_string_centered("Before Program Deployment", width, ' ') << std::endl; std::cout << std_string_centered("", width, '*') << std::endl; std::cout << "Running Unit Test..." << std::endl; unit_test(); std::cout << "Unit Test Passed." << std::endl; std::cout << std_string_centered("", width, '*') << std::endl; std::cout << std_string_centered("After Program Deployment", width, ' ') << std::endl; std::cout << std_string_centered("", width, '*') << std::endl; // Our program should work fine by user before upgrade. std::cout << "User Starts to Work..." << std::endl; work_on_color(get_color_from_user()); std::cout << "User Finished Work Successfully." << std::endl; }
A Working Million-Dollar Business Program With Dead Code
To compile the program before the upgrade, we will define the macro ORIGINAL to GCC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$ g++ dead_code.cpp -o dead_code -Wall -Wextra -Werror -DORIGINAL $ ./dead_code ******************************************************* Before Program Deployment ******************************************************* Running Unit Test... Doing some million-dollar business on color red... Doing some million-dollar business on color green... Unit Test Passed. ******************************************************* After Program Deployment ******************************************************* User Starts to Work... Doing some million-dollar business on color green... User Finished Work Successfully.
Notice that the function work_on_color_others and its call in the work_on_color function are dead code. Even if the function work_on_color_others has a severe bug, it will never be triggered during the runtime, and our business will still work fine.
Upgrading The Million-Dollar Business Program With Dead Code
At some time in the future, we decided to upgrade our program by adding a new color BLUE. The sloppy developer, however, forgot to add a new unit test for the new color. Because the existing unit tests will still pass, the reviewer approved this change and this change got merged and deployed.
To compile the program after the upgrade, we will define the macro UPGRADED to GCC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$ g++ dead_code.cpp -o dead_code -Wall -Wextra -Werror -DUPGRADED $ ./dead_code ******************************************************* Before Program Deployment ******************************************************* Running Unit Test... Doing some million-dollar business on color red... Doing some million-dollar business on color green... Unit Test Passed. ******************************************************* After Program Deployment ******************************************************* User Starts to Work... Doing some million-dollar business on color others... terminate called without an active exception Aborted (core dumped)
Right now, the function work_on_color_others and its call in the work_on_color are no longer dead code. The severe bug in the function work_on_color_others will be triggered when the user starts to work on the new color BLUE.
This means that after upgrade, even if the existing unit tests got all passed, the program becomes vulnerable to the severe bug in the function work_on_color_others that’s previously dead code.
Upgrading The Million-Dollar Business Program Without Dead Code
If the dead code was removed in the first place, there will be no severe bug triggered when the user starts to work on the new color BLUE. (There might still be other bugs though because no action was performed on the new color BLUE.)
To compile the program without dead code, we will define the macro REMOVE_DEAD_CODE to GCC.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
$ g++ dead_code.cpp -o dead_code -Wall -Wextra -DUPGRADED -DREMOVE_DEAD_CODE dead_code.cpp: In function ‘void work_on_color(Color)’: dead_code.cpp:76:12: warning: enumeration value ‘BLUE’ not handled in switch [-Wswitch] 76 | switch (color) | ^ $ ./dead_code ******************************************************* Before Program Deployment ******************************************************* Running Unit Test... Doing some million-dollar business on color red... Doing some million-dollar business on color green... Unit Test Passed. ******************************************************* After Program Deployment ******************************************************* User Starts to Work... User Finished Work Successfully.
The compiler will even warn the developer that the new color BLUE is not handled in the switch statement, if appropriate compiler flags are enabled. In my case, if I used -Werror, the program would fail to compile until I handled the new color BLUE in the switch statement in the work_on_color function appropriately.
1 2 3 4 5 6
$ g++ dead_code.cpp -o dead_code -Wall -Wextra -Werror -DUPGRADED -DREMOVE_DEAD_CODE dead_code.cpp: In function ‘void work_on_color(Color)’: dead_code.cpp:76:12: error: enumeration value ‘BLUE’ not handled in switch [-Werror=switch] 76 | switch (color) | ^ cc1plus: all warnings being treated as errors
Someone might still ask, what if the function work_on_color_others was tested and bug-free in the first place (note work_on_color_others is no longer dead code because it’s covered by tests), even if its call in the work_on_color function was dead code? Keeping the dead code can still be dangerous. It’s because the bug-free function work_on_color_others might not work correctly in the context of the new color BLUE, resulting in another kind of problems.
Conclusions
Taken together, dead code is dangerous because it can be triggered by future changes in the codebase. Even if the functions that the dead code calls have been well tested and bug-free, the dead code can still be dangerous because those functions might not work correctly in the context of the future changes.
As a result, dead code should always be removed from the codebase.