Getting rid of unused code with UCDetector

Introduction

The Java code base of TIM Funds (one of our products here at TIM Group) is around seven years old and in very good shape, considering its age. (This is a highly subjective statement as I joined the company less than a year ago. I am comparing with other Java projects of similar age and size from previous work experience.)

Nevertheless, cruft inevitably builds up over the years, so constant effort is needed to keep it down to a minimum. To accommodate this, we follow the boy scout rule while developing new features – trying to check-in code that is a little cleaner than when you found it. Furthermore, we have made room for a separate ongoing track of work named Sustainability, which is intended for concentrated efforts on removing problems that we have spotted in the current code or keeping our automated tests fast and flicker-free.

A couple of weeks ago, I was working on the Sustainability track, trying to refactor CollectionUtils, an old custom utility class with lots of public static methods that we’d like to remove. While doing this, I noticed that many methods had become completely unused over time, probably because developers had replaced their calls by using Guava, our collection library of choice. I found myself repeatedly pressing Ctrl-Shift-G (the Eclipse shortcut for finding references) to be able to see what can be safely deleted. After a short while I got tired of this and assumed that somebody must already have had the same problem. As usual, Stackoverflow knew the answer – several posts around this topic mentioned UCDetector, an Eclipse plugin which detects unnecessary public Java code. It looked like what I needed, so I gave it a try.

Configuration of UCDetector

My first impressions after installing UCDetector were mixed – although I only ran it on a package structure only containing around 50 classes, it was quite slow and produced an overwhelming amount of warnings with many false positives. (For example, code only called via means of reflection cannot be detected reliably.) The default settings of the plugin are quite lenient. They try to find many possible problems at once, but this way you can’t see the wood for the trees.

Fortunately, the plugin can be configured in many ways. It is worth spending some time finding appropriate settings for your project (in Window→Preferences→UCDetector). The suggestions below should get you started:

  • Ignore all warnings for possible use of final and visibility (tab Keywords). Your mileage may vary, but this increased the number of warnings considerably and gave some false positives for me – I consider this to be a slightly different aspect of cleanup and would rather do it separately.
  • Ignore code with annotation Override (tab Ignore, section Marked code). This is a compromise that might result in some truly unused code not being detected. You may want to try leaving this out, but I got a lot of false positives when detecting code only called by tests without it. For example, we implement Guava’s Function interface quite a lot and only call its apply method directly from tests.
    • If using Guice, add Inject to the list of ignored annotations (these members are called by the Guice injector).
    • If using Jersey, add GET, Path, etc. to the list of ignored annotations (these methods are called reflectively by Jersey)
    • Same goes for other annotation based third-party libraries.
  • Refine the settings by scanning some packages, examining the warnings, deleting some code and running your tests – you may find more incorrect warnings and need to extend the configuration.
    • For example, I had to ignore all classes extending our custom class PersistableEnumeration (tab Ignore, section Classes), because methods of sub-classes will be called reflectively via Hibernate mappings.

After these initial adjustments had been made, UCDetector worked really well for me. The scanning process finished significantly faster and the number of false positives was greatly reduced. I found UCDetector’s feature to warn about code only called by tests especially useful, because these cases are even easier to overlook when checking manually. Two short usage recommendations:

  • For bigger projects, prefer scanning package by package, not everything at once. This way, you get faster feedback and can concentrate on one area of the code (different packages might even need different settings). It is a good idea to keep the check-ins small anyway – I broke the build once or twice when starting out…
  • After removing some code, scan the same package again because more code might have become unused (domino effect).

Findings

My personal “kill count” after two or three hours spent with deleting code (spread across two weeks, using the very restrictive configuration mentioned above, and only covering under 50% of the code base): over 40 complete files (classes/interfaces), over 60 public methods, and even one database table (the corresponding Hibernate entity implemented an interface whose methods were all unused).

Although the number of deleted files was surprisingly high for me, I still stand by my statement that the code base is in good shape for its age. So how come there is so much unused stuff anyway? Looking at the source control history for the findings, it seems there are three main scenarios that are likely to leave unused code around:

  • Removal of functionality: Like everywhere else, requirements for TIM Funds change over time. Some features become obsolete and need to be removed. It is easy to forget or overlook to delete some service that was only used by the obsolete feature, because you don’t get any warnings from Java or the IDE itself. The one unused database table belongs in this category.
  • Bigger refactorings: So far, I have had most “success” deleting code within our file import component. This is an area of the code that people were never completely happy with and different people had different opinions about. Consequently, several attempts to improve the code were started. Now there is a “new way of doing it”, but several “old ways of doing it” were still lying around. This was true on a smaller scale in other areas as well.
  • Introduction of new libraries/frameworks: After we introduced Guava and started replacing calls to CollectionUtils, these methods gradually became unused. Another case is replacing the functionality of a third party library (Apache Commons) with a different one (Guava), likely making the JAR unused in time. We still have to tackle this particular task for our code base. Other tools like Classpath Helper are needed to fully help us here.

In hindsight, all this might sound rather obvious, but it’s worthwhile keeping these three scenarios above in mind. They provide good opportunities to start up UCDetector and delete some unused code, which is always useful, often quite educational, and sometimes even fun.

Leave a Reply

Your email address will not be published. Required fields are marked *