Note: this article was previously published on Damian’s Medium.com profile. The author agreed to republish the article on the Schibsted Developers Blog.
Do you know that feeling when the day of late payback of debt comes? This unpleasant feeling in the stomach, shame and humiliation? I don’t. I pay my debts on time. 😉 But I believe that it’s the same as with technical debt, that we — developers, have to pay.
I know that refactoring is not a new topic, but maybe you are new in programming, and you still believe that applying some popular patterns and libraries will make your life easy and programming smooth as this:
WRONG !!! You will make mistakes, and the nightmare will never end! 🙂
Of course, I am just kidding — it’s fine to fail, make mistakes and learn from them. Still, you will leave behind some things that you are not proud of, and when it comes to fixing them, my advice can come to the rescue.
Note: Everything I am going to write below is based on my personal experience.
1. Baby steps
When I started 2,5 years ago to work as a full-time Android Developer, I wasn’t that much experienced with all the cool stuff I know now (I am so humble, am I? :P).
Before that it was a mix of different platforms. So I started this project, Omni. I decided that I will use all the best practises from Android community. Unfortunately, I didn’t know the business that well yet, and all the features that I am going to implement. So after some time I end up with different approaches in different places in my code.
Horrible!
Today my code looks much better, but I didn’t change everything at once. I was always trying to split a one big task into smaller ones. If it’s something you can do it in one or two days it should be fine.
For example: I was using API models across different layers of my app, starting from the MODEL to PRESENTER and finally VIEW. Yes, it was MVP :). I had to extend API models, just because view needed some extra information or I wanted to do some computation once after loading data, not when binding data to the view(RecyclerView).
If you think now this is a bad idea, you are right. So I decided, in presenter, to convert all API models to view models. It all sounds great, but I already had pretty extensive data structures, ~40 view item types in one RecyclerView. It’s crazy, I know!
Nevertheless, I had to start somewhere. So first of all, I introduced VMItem
interface as a base class for all my view items in adapters. Then I started creating classes implementing VMItem
that represents UI components and wrapping API models.
I will shorten one of the classes here to explain it better.
Kotlin is my primary programming language so code samples are written in Kotlin.
This is my API model representing an article:
data class Article(val id: String, title: String) : Item
For that class I have created view model:
data class VMArticle(val original: Article) : VMItem
Converting this was super easy as you can imagine and changing the usage in adapter was even easier, from:
override fun onBindViewHolder(...) { val article = data[position] textView.text = article.title }
to:
override fun onBindViewHolder(...) { val article = data[position].original textView.text = article.title
You may think it’s not a big difference…BUT
- This little change allowed me to start converting each item type (there was~40 in one adapter), separately whenever I found time for it without breaking the rest of my code.
- I was able to start removing, from API models, all properties that didn’t came from the response. And again, I could do it item by item without breaking anything else. Very simple approach.
So eventually, I end up with this class:
data class VMArticle(val title: String, val formattedDate: String)
Notice that dependency to API model is gone and I don’t need id
property that was not used in the view layer. Any usage of API models is kept within model layer during conversion. Last but not least I was introducing properties such as formattedDate
in the right place.
That has another pros, whenever backend guys decide to do API changes it’s very easy for me to apply those changes without compromising the rest of the app.
2. Discuss your idea with…duck(or anyone)
If you are an experienced developer, you heard about Rubber Duck effect. If you are new to programming, you better go to the wiki and read it. It’s true, it works and it’s perfect for refactoring. Among all great ideas I had a million bad ideas.
When I was alone Android developer, I tried to talk to colleagues from other teams or even iOS developers(sic!).
I also found drawing/visualising a great way to find bottlenecks of my ideas. It may work for you, if you really don’t like to talk 🙂 No need for sophisticated tools, just a piece of paper or whiteboard will be just fine.
These days, when there are two of us, it’s much easier. We both understand app context, so there is no waste of time explaining what is a certain solution about, and I can also get productive feedback.
3. No rush!
We are usually chased by deadlines and try to fix everything quickly. When we do, quality suffers, or even worse, we step into even bigger mess. So think twice before you start refactoring your code.
Is there enough time?
How much do I gain?
Is it really needed now?
Ask yourself those questions.
Also, it’s really beneficial to sit down with your team, write down everything you would like to change/improve, split into smaller parts and put it somewhere in your road map.
You may say: “My manager won’t give me a week for refactoring”. He probably won’t, but it’s not really needed.
When you group your changes, try to compare it to the task that you have approved from your supervisors/stakeholders and try to find a match. Developing a new feature is a great opportunity to fix something else around that code.
Just say that this task require a little bit more time to polish. You don’t have to hide it, it’s a good practise to keep a good level of code quality.
4. Be careful with code in specific folders of build types or flavours
Not that long ago, we had to develop new versions of the app, with different set of features, look etc.
Super easy, I said to myself.
Android support is very helpful with variants for which we can specify different resources and implementation. I shoot myself in the foot with the implementation part.
When switching between variants Android Studio, the tool does not recognise classes from disabled variants. That is maybe OK, when you start moving things there and write new features from scratch.
But later, when I had a lot of code moved I started some refactoring. IDE handle that without a problem, but only on code that it recognizes.
Eventually, once I changed something, I had to switch variants just to fix the rest of the code manually.
My advice? Hmm, I still have that code there.
Right now I am planning to move back everything to core module and based on variants pick appropriate implementation.
Dagger should be useful there.
Last advice
Remember, don’t be afraid of refactoring! It’s not a black magic, it’s a natural part of our job.
I keep saying to younger developers: “if you look at your old code, and think that it’s all good — there is something wrong with you. You are not improving at all.”
My old code always scares the s*** out of me 🙂