How many times in your life have you heard someone say – you know what it would be so much quicker if we rewrote the whole thing? Why does that happen and how can you fix it or better still prevent it? The answer is less complicated than you may think.
In this blog we’re going to look at the cause – Technical Debt – and the cure – Refactoring. We’re also going to look at Android projects as it seems to suffer more than most platforms from Technical Debt. It’s not uncommon to see Activities longer than 1000 lines of code making the apps almost impossible to maintain.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." - Martin Fowler
For a couple years now Android Studio has had the same refactoring tools as you can find in IntelliJ and Eclipse. To date I’ve seen very few resources that show how to refactor an Android project in Studio. So let’s show the basics so you can get started yourselves.
Why would you want to refactor your code? In business terms, as codebases age – because of all those easy solutions or short cuts – it starts to take longer and longer to add new features than new code . Over time as this Technical Debt starts to rise, it becomes harder to understand and harder to extend the code.
"Technical debt ..[is]... the implied cost of additional rework caused by choosing an easy solution now instead of using a better approach that would take longer". - Wikipedia
Figure 1 shows how the Actual Cost of Change vs the Optimal Cost of Change rises over time.
As the Actual Cost of Change rises sooner of later someone will say “You know what? it would be quicker if we rewrote the whole thing in Kotlin”. It doesn’t have to be this way, as we can pay down the Technical Debt by refactoring the code and getting a better Cost of Change.
What is Refactoring?
For me, refactoring is the process of simplifying your code, or reversing a lot of the decisions that caused the original technical debt. Here’s the official definition.
Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior. Refactor (verb): to restructure software by applying a series of refactorings without changing its observable behavior. - Martin Fowler
Before your start refactoring your code is often in a B design pattern. When you’re done it should be less complicated, i.e. less inter-connected and more object oriented.
So the first part of a cure is knowing you’ve got a problem, next we’ve got to figure out how to try to fix it. Best way to do this is to understand what the different types of Technical Debt or Code Smells are in your code. We can group them into the following categories:-
- The Bloaters
- The OO abusers
- The Change Preventers
- The Dispensibles
- The Couplers.
The definitions for each of these code smells can be found at the website. For example in The Bloaters we have smells the represent something that has grown so large it cannot be effectively handled like long methods and large classes. This sounds very like Android to me.
Objective vs Subjective
Code quality is not a subjective thing – it should be obvious that the coding style shown in Figure 2 isn’t a long term strategy to a successful product. Developing apps with Activities longer than 2000 lines is not sustainable and the speed of development is going to grind to a halt.
A good place to start is to look at a metric called the Cyclomatic Complexity. It is a measure of the number of linearly independent paths through a program’s source code. Figure 3 shows reporting from an Android Studio plugin called Metrics Reloaded. In this case I’m looking at a Google sample android app called XYZTouristAttractions.
MetricsReloaded is free and also measures Lines of code, MOOD (Metrics for Object Oriented Design) and Bob Martin’s Packaging Metrics showing how much coupling vs abstractions you have in your code.
To install MertricsReloaded, go to Android Studio->Preferences->Plugins->Browse Repositories and search for Metrics Reloaded, install the plugin and restart Android Studio.
What’s the Process
To run the metrics reports go to Analyze->Calculate Metrics which will generate a report similar to Figure 3. Sort the metrics from high to low by clicking on v(G) or Cyclomatic Complexity. This shows the methods that need to be refactored. In Figure 3, MetricsReloaded is complaining about the if / else if statements in the onHandleIntent method. It gives a value of 10 for both the Design Complexity iv(g) and the Cyclomatic complexity v(g) telling us we have a good candidate for some refactoring. Make sure you have good unit test code coverage and that there’s no failing tests and we’re ready to begin.
Table 1 shows all the Refactoring Actions available in Android Studio. There’s so many actions that we’re only going to focus on the most commonly used.
In Figure 4 we see how Rename is used to rename an object or resource.
Move refactorings, see Figure 5, allows you to move classes to other classes as well as inner classes within the project’s source tree. All references to the moved classes and members are corrected in the source code.In this example we’re moving a method from the Customer class to the Rental class. Always run unit tests after refactoring otherwise it’s too dangerous to make these sometimes massive changes.
When the Extract Method refactoring, see Figure 6, is invoked – Studio analyses the selected block of code and detects the input and output variables for the selected code fragment and creates a new method using these parameters. This is very useful when you’re trying to tidy up activities by first extracting the different actions taking place in an Activity so you better see what’s going on at the top level. Then if it makes sense your can move the methods to another class.
There aren’t many Android specific refactorings, but Extract Layout, see Figure 7, will allow you put some order back into your layouts.
Pull Members Up
The Pull Members Up refactoring allows you to move class members to a superclass or an interface, see Figure 8.
Push Members Down
The Push Members Down refactoring does the reverse by moving class members to a subclass, see Figure 9.
Convert Anonymous to Inner Class
When the logic gets too complex, use this action to create an inner class, see Figure 10.
Replace Temp with Query
Replace temp with query replaces all references to the temp (variable) with a call to a method, see Figure 11. The resulting code by definition is going to have more invocations or method calls, however this helps identify precisely where the bottlenecks in the code can appear.
There are a few simple lessons learned that we learned while we’ve been doing refactoring on Android apps. These are as follows:-
- Always create unit tests
- Use the metrics as a guide not as a mandate
- Measure early, measure often
- If possible refactor with another developer
There is currently a strong inclination in Android teams to push for a complete or partial rewrite in Kotlin. After all Java is the new Eclipse in the world of Android. If you’re doing it for the right reasons then that’s fine. But if you’re doing it because your code is a , then you may want to think twice and instead refactor the existing code to lower the Cost of Change and gain some extra life from your code. Otherwise you’re just going to be in the same boat in 18 to 24 months, with other people calling for a rewrite, just this time in whatever is the all the rage after Kotlin.