The martial art of refactoring (Part 2)

Stressless work

Svyatoslav Hromyak
TEMY
8 min readDec 25, 2018

--

Léon Joseph Florentin Bonnat, Samson’s Youth (1891)

The first part was dedicated to project infrastructure. Hope it helped to answer many questions related to a project bootstrapping. Now is something more interesting: how to survive.

The matter of the problem

Pieter Bruegel, The Elder Leading the Blind (1568)

I heard this many times from experienced developers, guru of the programming: “Oh, please don’t look at my old code — it’s ugly. Let me show you something from my personal repository.” or “We used MVC (…or other stuff), we know that we weren’t right, but will try to fix it ASAP” or even worse: “This part of code is pretty cool, because we decided to overtime a bit to eliminate our technical debt, but our client knows nothing about this stuff”.

That’s bananas: we are trying to do something important and useful, hiding it out of client’s sight at the same time. So what is the point here? Why should appear this technical debt mentioned above? I won’t repeat a long list of reasons, but just a most dangerous one: the time pressure. Why dangerous? — because it’s source of a burnout as well.

You should accept two things:

  1. Bugs are inevitable — don’t fight with them all the time (please take the most repetitive five from the list).
  2. Things happen — not everything is under your control.

From the client prospective it sounds like this: “I paid this feature few months ago. Why should I pay for this again? — it’s only a slightly difference in the design”. Please look attentively, it’s not an ultimatum — it’s just a question that you need to explain, again and again.

In my opinion, the main problem is not a technical debt itself — it’s rather lack of transparency and miscommunications. And again elimination of the technical debt is continuous process that isn’t avoidable and you shouldn’t sacrifice yourself.

When should I stop coding?

Ilya Repin, Barge Haulers on the Volga (1873)

Once upon a time, there was a famous artist Ilya Repin and every time gatekeepers were instructed that he wasn’t allowed to enter his exhibition with paints and brushes (otherwise, he polished his masterpieces on the go). So you’re not allowed to do this either.

In most cases projects have external events related to certain dates, appointments and you can be lucky when you are allowed to buy some time to delay the delivery, but in the reality you can’t. It’s good to have the possibility to split your work in two to be ready for partial delivery in case of emergency. Your final target is a demanding customer, please imagine that he is a stalker that knows where you live. This picture motivates you to make a decision about what exactly should be delivered once a deadline comes. Please shift the deadline back to be sure that you’re able to complete your work in time. Sometimes pushy product owners are so needy — don’t hesitate to resist them.

Pony sprint

Eadweard Muybridge, Animated sequence of a race horse galloping (1897)

AGILE is cool modern word in IT industry. If your management declares AGILE principles, forgetting about the whole picture — it’s definitely not an AGILE. One minute of this video can explain everything about this issue.

Every team should agree about priorities in their work. Otherwise your JIRA tickets would turn into the mess any moment. And it will be real pain in the neck and burnout hazard.

Since start of the project we have agreed to use a SCRUM. However we were struggling at the beginning because in our sprints all the time appeared tickets marked as “critical”. Thus our work was more like Kanban than SCRUM.

Let me give you an example. Assume we have two features that should take us about one and half weeks (agreed sprint slot was 2w). During that time all of the sudden appears new requirement that enlarges development so testers couldn’t start their work in time. In this case we have “bug overflow” just before planned release. Should we sacrifice one of the feature to complete on 100% one of them?

Actually there are multiple solutions how to solve that. For example, we can plan one feature for half of sprint splitting it between whole developers. So just after it’s completed we could start new feature for the next sprint. But not in our case, our management likes when plate is full all the time. So we found another solution.

For the major features we had a full sprint dedicated for the development. Each developer takes one feature from start till completed. Such approach allows us to save some time for coordination during feature development. Other hand, we had such thing to be in tune:

  • grooming (just before sprint planning),
  • daily synch ups,
  • continuous code review and
  • at least one tech talk weekly or on demand in case of some struggles.

We also had “15 min rule” — to ask external help in case of stuck that lasts more than 15 minutes. When everything is done, we planned and started next short sprint. It has name “pony sprint” and was aimed to fix, polish and optimize features completed at the main sprint.

In most cases pony sprint starts from fixing of TOP-5 bugs from Crashlytics. During that time testers started their work and new bug tickets start to appear in the board in release critical swim lane. Other words, pony sprint seems to be more like Kanban in our case and it works.

Was it against the rules? Definitely — yes. But if you read attentively the point is “all of the sudden appears new requirement”. This thing is actually against the SCRUM rule. Such interrupters are disguised as AGILE — but in fact it’s not an AGILE as was explained below.

So if you have same struggles I would recommend you to use “pony sprints” — it’s hybrid solution combining SCRUM and Kanban.

Reasonable refactoring in action

Kandinsky, Composition 8 (1923)

When we started the project we couldn’t push our own decisions that much. Mostly things were predefined by third party consultants, hopefully they were experienced in Android development, so their main suggestion was to proceed with native app based on MVP. We had thousands of users that used hybrid app at the time (with minimal Android SDK 15), it was our first involvement with the product owner, in other words, there was no room for mistakes.

Decisions were taken in order to use a possible maximum conservative development strategy. At the moment it was: using standard Android components, no cache, no database, no screen rotation at least during the MVP stage. We used an Android Service system component and ThreadPoolExecutor for API requests, minimal set of third parties (Retrofit for instance), UI was based on multiple activities bound to the service, plus headless fragment as a Presenter. We also used Weak References to prevent memory leaks, plus Idling resource implementation for Espresso tests. This approach was described in Google’s official documentation that we could easily refer to. The main reason for using multiple activities — was design. It contained translucent status bar screens scattered over the whole project.

Of course this approach is thousands lines of code on Java, but the trick was to use automatic code generation. Because all endpoints had been working for the hybrid app already and all requests were executed in service — which is a pretty isolated system component we implemented (in 3 days) our own code generator similar to jsonschema2pojo. It was unattractive, but allowed us in 2–5min to cover any endpoint with data classes, all the necessary RetroFit interfaces and instrumentation tests (all-in-one). So most developing time was spent linking data to the UI components.

Firstly general refactoring were driven by performance optimization. We decided to get rid of the Service. The Android binder is sluggish and by using Retrofit directly from a presenter proved to be the best solution for that moment. Another a good feature was to use RxJava2 and Dagger 2 instead of a custom executor. Finally we got rid of activities by keeping only two (main activity and checkout). All our UI components were based on the fragments since at that moment. The translucent status bar theme was swapped by a black status bar theme which was more easy to handle with Activity recreate method. Of course refactoring cannot be provided with the wave of a magic wand — it was covered by new feature implementations. Many advice on how to do that can be found in Working Effectively with (Android) Legacy Code and Refactoring Catalog.

Second general refactoring was related to revolution inside the Android platform: Arch Components and Kotlin. At the same time Google officially admitted that using headless fragments wasn’t the best approach, so we were right in refactoring it. Once Arch Components became stable we decided migrate to it. We didn’t migrate to coroutines, because RxJava2 suits all our demands, but we improved this approach by using Data Binding (MVVM) and LiveData in pairs. I will attempt to explain this in a separate article on how we managed to combine them using clean architecture and data driven development.

Recap

Pieter Bruegel, Two monkeys in chains (1562)

There are always many things to improve. First achievable goal in our case to complete phase 2 for all features and to move to the Android Jetpack announced by Google nowadays. But each goal demands time and effort. The most challenging thing is to tell yourself: “Stop refactoring — feature first”. Everything you do is for your clients after all, and .. one of them is, for sure, a stalker!, please don’t forget this ;)

--

--

Svyatoslav Hromyak
TEMY
Writer for

Lead Android Developer — Mobile Architect, Mobile Competence Leader