This week I removed a big chunk of code from our app, an entire section/feature. I've been working on this codebase for years and I had the opportunity to do a bunch of cleanups. Thanks to that I've organically developed a strategy to remove code that is quite successful to me.
The goal of my strategy is to make sure everything is gone, that no traces in the codebase of this old feature last. This is important to understand because this technique doesn't favour speed so doesn't expect tips around that.
Before we go into the tips, of course I must give you an important recommendation: modules.
If you split your features in different modules you will have an easier time getting rid of them. But you won't always have this privilege, specially in older codebases.
But even in the cases you have, maybe that module has dependencies or assets that are pulled from somewhere else that you also want to remove.
That's why I still use this technique even if my code is properly modularised.
Step 0: The note
The first thing you should do is open a note on your notes app of choice.
This technique relies evenly on keeping track of things in a note, so make sure you setup your environment to quickly jump between your IDE and the note. Multiple screens, split windows, shortcuts… whatever works best for you.
The only small requirement for the notes App is that it should allow you to paste code without messing with the formatting or quotes. You will need to copy the code from the note later, and if quotes are messed up it will be a pain to find what you're looking for.
Step 1: Removing code and Gathering dependencies
Delete one file at a time: Removing an entire folder is quicker but I assure you there will be dependencies outside that folder that you will forget to remove. Go file by file skimming the code looking for dependencies, strings, assets, etc. Your IDE should color strings differently so they will be easier to catch.
Gather the dependencies, don't remove them yet: It may be tempting to remove every string or other dependency as you find them. But that constant change of context will make you slower and is really error prone. Furthermore, that dependency may be used somewhere else so it shouldn't be removed. Remember that note that I mentioned? Write down every dependency on the note.
If you're removing code in a language with headers you're in luck! The explicit imports will help you trace dependencies down. Copy and paste each import line in the note for later. And don't bother about duplicates for now. Just paste it all there, at this point you don't have to worry about the dependencies, you just gather them all.
If you're using a modern language without headers, you still can do the same with external modules. For your own code dependencies is gonna be harder. Try to keep an eye open for code that may seem only used in this feature. This is often the case with helpers that are used in different parts of the codebase but with time they ended up just using in one place, if this is the last place they should be removed!
Do the same for strings, assets or other external resources, but make sure you keep each kind of dependency in a separate list, you don't want to be jumping around on the next part.
The goal of this step is to gather all the dependencies in the note while removing the code. It is important that you don't act on the dependencies yet.
Step 2: Remove dependencies
Once you've finished removing all the files from your feature you will likely have a note with a bunch of dependencies: modules, file imports, strings, images, etc. Now is time to get rid of them.
Start with code dependencies because this ones are likely to surface more dependencies, and because the compiler is gonna help you more than with the rest.
One important thing to keep in mind is that during this process you may find other code that you can remove, when that happens keep in mind the methodology. Remove the code, but just gather the dependencies. Iteration!
Before continuing with any of the list, I recommend you to sort them alphabetically. Your editor will probably have a command to do that, and if not copy paste the list in an editor that has it or some website. The advantage of having the list sorted is that you can very quickly see duplicates and reduce the times pend in the following steps.
If you have a list of imports, for example in Objective-C, paste them all in a header file and run the compiler. Any import that the compiler can find will mean that is for a file that you removed, good job!
For the imports that the compiler finds you can to a search for those types and check if they're still used in another part of the codebase. If the only result is the import you just pasted you're in luck, you can remove that code too!
Now that all the code is removed, you can work on strings, images and other assets. You can just do a search for each item on the list and see if they have any other usages. If they don't it means is time to remove them! Your users will be happy that the App has shrink in size ^^
If the assets are used in other parts of the code I still recommend you to check them. The usage of those assets may mean that that code can be also removed!
Step 3: The PR
Once your job is done is time to PR the changes into the main branch. The resulting PR will likely be huge and your colleagues may quickly skip it and approve it, after all is just removing code ¯\(ツ)/¯ . WRONG!
First of all, during this removal you're likely to modify slightly some parts of the code to avoid using stuff that you removed (maybe an enum case doesn't make sense anymore, and that affects some switch statements). Those parts must be reviewed carefully like in any other PR.
But even more, ask your colleagues to take a look at the files that have been removed, this technique is not infallible so the more eyes there are looking at the PR the more likely it is to find something you missed.
Making sure everything is removed now is important. Yes, you can always do it in some months when you find that something is not used, but when that happens nobody remembers why that thing was there in the first place, and nobody is sure if it can break something else. When that happens in the middle of something more urgent the result is that nobody removes anything. Leaving dead code or assets in the codebase for even longer. You're gonna regret it, and your users won't be happier.
Because this is a tedious process I recommend you that at the end you have some fun.
Before merging the PR run some analytics to count the number of lines you removed from the codebase. You can use GitHub numbers but if you want to go into more detail I recommend you to manually run tokei or any other similar program of your choice.
The last feature I removed was an important portion of our codebase, specially in terms of assets. More than 200 files and 20k lines of code.
GH may be a little misleading, as that includes strings, an external Pod and tests.— Alejandro Martinez (@alexito4) August 16, 2019
Checking only production and test folders (ignoring Pods):
209 files removed
23,177 lines (including blanks and comments)
15,873 lines of code
And that was before one of my colleagues found more stuff to remove! That's why the PR is so important! ^^
A quick note on tools.
It's likely that there are a bunch of tools that check for dead code, unused assets, etc. I recommend you to use them but don't rely on them. Those type of static analysis tools often miss things in complex codebases or if you do anything at runtime.
- Open a note
- Delete code. Gather dependencies like other modules, helpers, strings, assets...
- Delete the dependencies.
This may seem like a longwinded way of doing this task. But after doing it many times over the years and always missing stuff I've organically developed this series of tips to try to reduce what's missed.
If you have any other tips don't hesitate to send them over! ^^
And be happy! You removed some code! The best feeling eva :P