As our code evolves and changes, little bits of leftovers from refactors or changed paths can build up as dead code. It’s like the plaque that builds up on your teeth over time: not really noticeable at first, but after a trip to the dentist, you feel a whole lot better.
Removing dead code from your codebase is a simple process at its core: find the dead parts, prove that they’re dead, and delete them. The tricky part is in that second step, but after a little trial and error, we discovered a couple tricks to speed up the process.
Before digging into a gigantic repo to see what you can rip out of there, consider the impact of what you’re about to do and start with just a small piece of it. Because I was investigating one of our biggest repos, I decided to tackle a single Model first rather than, say, the entire lib directory.
Finding the dead parts
I’m sure there are other gems out there that perform similar assessment, but our favorite gem for this is debride by Seattle Ruby Brigade. It’s not 100% perfect, but it will give you a good place to start digging around. The whitelisting option is particularly helpful after your initial investigation. Another option is the brute-force method, where you comb through a file and investigate each piece one by one. If your
debride output is suspiciously large, this might be worth a try.
Prove that they’re dead
This is the fun part.
The specific service I was trying to clean up is one of our oldest repositories, and some of the functions that were showing up as dead ends had been sitting in there for months or even years. What’s a girl to do, dig through hundreds and thousands of pull requests and commits until coming up with the right one? Oh no, we have a tool for that. It’s called git-bisect, and it’s one of the coolest things I’ve learned so far about git.
git bisect is generally used for tracking down when a bug was introduced by using a binary search through as many commits as you’d like; you can start at the very beginning of your git history or somewhere in the middle. It checks out the repo at each commit, where you can test your issue and mark it as “good” or “bad.” Rather than testing for a bug, though, I used it to
grep for the method names that were appearing as dead from my previous step one at a time. If
grep only gave one result (barring tests that might still exist), then nothing else was calling the method anymore and I’d mark it “bad.” If it showed up with more results, I’d mark it “good.” The point when searching for a bug is to find the last “good” commit before a bug was introduced; the point when using it to look for dead code is to track down when the consumer was removed.
Here’s an example to illustrate the steps you’ll take to do the same thing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
As you can see, I finally found the commit where the diff showed the changed or removed method call; thus, I could remove the call in my current branch. After deleting all the dead code, I made sure to comment on each removed function in my pull request with the commit number where the consumer was removed (because documentation is rad). Many code reviews later, our Model was done with her teeth cleaning and felt just a little bit better.