Well, not quite the entire app, but pretty significant parts of it. The Dashlane iOS app has been around since 2012. Eight years is a long time for an app. Throughout those 8 years the original Objective-C codebase remained. We jumped on the Swift bandwagon around 2016, that helped us break down our monolithic app into neat frameworks, allowing among other things better test coverage. However, a lot of the core parts of our app were mired in outdated and convoluted code.
So how did we go about this re-write? And why didn’t we just refactor in place, piece-by-piece? Grab a coffee, rm -rf your derived data folder, start compiling and read on!
The early years
The Dashlane app was originally a companion mobile app to our desktop one. At the time, iOS was not as elaborate as it is today, there were limited opportunities for a password manager app to provide useful features to users beyond copy and paste or a hacked together web-view that allowed something that resembled autofill.
As a company, Dashlane was still pretty young and determined to avoid the doomed fate of 90% of all start-ups (made up stat, possibly not far off). We didn’t have great practices, no code reviews, not even branching, we all just worked directly on one branch. I say “we all”, of course at the time we were just a handful of engineers who lunched together every day.
Fast forward to 2020
Along the way we picked up a whole lot of teammates, three offices in three countries, and a whole bunch of users, all helped along by piles of investor cash. We made it. We didn’t fail as a startup, but our ambition is just as strong today as it was in the early years, and we’re not satisfied with what we’ve achieved—as great as those things are.
Oh yeah, and we’ve been doing code reviews for a while now.
Our little Dashlane iOS app also grew up. Apple furnished us with more opportunities as a password manager, first with extensions for Safari, and then with the ultimate feature for a password manager: Password AutoFill.
All the innovation and improvements were good. But it also meant we had accrued a big stinking pile of tech debt, dating back all the way to 2012.
How did we get here? Why so much debt? Looking back, it’s hard to spot occasions when we had opportunities to pay down tech debt as business priorities didn’t leave much room to tackle it. In the password manager space we were the new kid on the block, and in the early years we had a lot of ground to cover to catch up with the competition. Then we had to stay ahead of the curve as the world became familiar with security breaches and the chaos of weak passwords and hacked accounts. All the while our tech debt grew, and we could not do much about it.
Lately this become a challenge to the growing team and the evolving technological landscape. How can we retain engineers who have to go down the rabbit-hole of an undocumented and incomprehensible legacy codebase where some classes had line counts measured in the thousands? How can we keep up with the evolution of Apple technologies like SwiftUI and Combine if we were stuck with aging, confusing codebase, one that very few people in the team mastered?
We needed a drastic solution. We needed a re-write.
We didn’t start from scratch, though we did have the satisfaction of hitting Xcode > File > New > Project… and selecting “SwiftUI” rather than “Storyboard.” We started over, but we had a lot of good recent code we could reuse. When we moved to Swift we created some neat frameworks, and these could all be dropped into the new app. Some important parts of our app had already been re-written in recent years, code that deals with our cryptography, our internal data sync mechanism, and our local storage tools. We also made architectural choices that allowed us to drop in some of our UIKit features alongside new SwiftUI based ones. We had a running start with the re-write, but the road was long; it took six months for us to reach a point where we could ship an update in the App Store.
Re-developing an app, while maintaining and evolving the existing one in parallel carried plenty of risk. Every week that went by added to the ever-growing difference between the new app and the old one, which raised the risk of things going horribly wrong.
We had a team of four developers who worked on the re-write, while around five others evolving the existing app. How did we manage this? By making hard choices:
- Certain areas of the app had to be frozen during the re-write.
- Some teams had to continue to build on top of the old app even though we knew we would have to re-do that work on the new app. These features were just too critical to the business to wait for the refactor to be complete.
- We cut out some features from the app in order to get to the finish line, once we released the new app we could work on adding these back.
- Any complex migration code was avoided where possible, migration and risk seem to go hand-in-hand.
Every merge request had to be labelled to indicate if it would need to be re-implemented in the new app. The entire team was well aware of the re-write project and code reviews helped share knowledge of what was going on with both apps.
The other risk that comes with a re-write is the bugs and gotchas, dealt with over the years, that have been forgotten and could come lurking back to bite you. We were never going to catch all the issues. However, to mitigate some of that risk we made good use of TestFlight. This allowed public users to test our unreleased app. We asked our CRM team to invite 10,000 users to sign up to our TestFlight beta. In total we had around 600 beta testers sign-up, who were active in providing feedback. TestFlight feedback isn’t well integrated into our workflow yet, so it meant manually going through the feedback on the Apple website. Nevertheless, public beta feedback was crucial, and we discovered and fixed bugs not uncovered by QA nor dog-fooding. There are few things more satisfying than hearing about a bug from a user, fixing it, and then hearing back from the user: “It works now!”.
In August 2020, we finally shipped our re-written app. We had a few bumps along the road, and four hot-fixes later, we made it. So six months to deliver what looks like the same app to our users, what do we have to show for it?
- Crashes – 50% fewer crashes.
A lot of our crashes stemmed from our archaic architecture and legacy codebase no one dared to touch.
- Performance – 50% faster to load an account.
Getting the user to their data as fast as possible is absolutely critical for a password manager. The re-write give us opportunities to build performance in from the start, and optimise more easily.
- Re-engaged team.
As a manager this is the topic that makes it all worth it. We now have a team that can actually make sense of the sprawling codebase. They can share ideas more easily and can evolve, maintain, and improve the app easier than ever before.
Deciding to go down the path of re-writing major parts of our app was not an easy decision to take. The risks were daunting. We had an eight-year-old app which was regularly updated, every two weeks, and in parallel its replacement had to be birthed over six months. When you’re working on long project, you’re exposed to plenty of opportunities to have your plans knocked off the table. Half the battle was protecting the plan and keeping the project on a steady path, maintaining a solid momentum, and a motivated team. Rarely does everything go to plan, and the lesson we learned is that you have to stay focussed and learn to navigate around the unexpected hurdles, always keeping an eye on the prize. Like any debt, there is satisfaction in paying it down. We look forward to moving into our new home, our new app, and fitting it out with new features, where everything fits neatly into place.