Welcome to a short series of blog posts on how we handled a great deal of ever increasing tech debt at Base CRM iOS app, and used automation to prevent it from growing again. In this introductory part, I'll give you an overview of our codebase and share a few insights on how we resolved all the compilation warnings we've had. Have a read to get a grasp of what can happen when you work hard on new features and limit maintenance work to bug fixes.
We've recently released version 3.0 of Base iOS client. A big achievement for us, but let me put it in context so that you can realize it too:
- timeline: when I joined Base 5 years ago, we were (already) at version 2.4, and the latest release before 3.0 was 2.57.3. We used to be pretty productive, releasing a new minor version roughly every month, but for over 5 years we've been iterating over the same UI and data concept. Now version 3 meant a thorough rework of the core components on both UI and backend side.
- workload: the 3.0 release hit the AppStore around 20th Oct 2017, and we started working on it around Christmas 2016. While our iOS team is normally split between several projects running in parallel, this release required the whole team to work together most of the time.
Now that you have the idea how big an effort it was, it makes sense that after the 3.0 release we all got a couple of weeks to recover and do some housekeeping, instead of jumping into new projects right away. This was a perfect time to take a closer look at our codebase, fix old warnings, verify weird project settings, get rid of unneeded code and possibly improve the build system.
The size of the code
For reference, here's the output of a
cloc run on the Base iOS client v3.0.0 tag, excluding pods, submodules and tests code:
[caption id="attachment_301" align="alignnone" width="1297"] Seriously, I'd love to know where's the D language code :)[/caption]
Behold the picture, that is worth a thousand words:
No, it doesn't include pods warnings, just our own code.
Yes, it's a shame.
It obviously didn't suddenly grow from 0 to 400-ish overnight. As you know, a number of APIs get deprecated with every major iOS release and it's not always a straightforward fix. So it gets postponed to after the release, but in case new project kicks in, you end up with no fix at all. Even worse, a couple of times we fixed some of them only to have the pull request wait forever for merging because it touched N+1 places in the app and required a thorough testing, and there was no time to do that because we're releasing like crazy all the time.
400 warnings not only make Xcode's toolbar look bad, but it also renders Issue Navigator almost unusable. It's very hard to scroll through the list of build issues, plus you're very likely to miss new warnings that you might be just adding. Well, apparently Xcode developers didn't optimize for that amount of issues, and perhaps this time they're not to blame (!). I mean, this shouldn't have happened in the first place.
What's in that number? The usage of deprecated API, like
UILocalNotification, and legacy frameworks like AddressBook and CoreTelephony accounted for around 70-80% of all the warnings. The rest was other one-off API deprecations, nullability specifiers issues, and minor syntactic inconsistencies. Then the Swift 4 reduced
@objc inference, and the trickiest one to fix in a large codebase:
ld: warning: Some object files have incompatible Objective-C category definitions. Some category metadata may be lost. All files containing Objective-C categories should be built using the same compiler.
It boiled down to going through Swift extensions of some Obj-C classes and replacing
@objc static var definitions with
@objc static func. Some, meaning those included in iOS SDK, or more precisely, those built elsewhere and linked with the project as binaries (i.e. not necessarily built using the same compiler). The tough part was that the offending definitions weren't pointed out by individual warnings, but they had to be looked up manually.
I don't want to waste your time writing too much about warnings, especially that most of them should have been fixed 1 or 2 years ago and it's non-news as of today. The most important point about our warnings crusade though is that after some solid effort we managed to bring the warnings count down to 0! Including the funny last one, that we actually kind of gave up on:
[caption id="attachment_302" align="aligncenter" width="300"] Blast from the past.[/caption]
The thing is, we don't even use launch images, as we replaced them with Launch Screen storyboard. We don't have launch images at all in the project. My bet is that something is wrong with the project file – after all, it dates back to iOS 5... Perhaps we could fix this by creating a new project from scratch and re-adding all the files and settings, but there are no volunteers for that yet, so we ended up adding the placeholder file. It doesn't even show up on the iPhone 5S, but most importantly Xcode is happy now.
Fixing all the warnings in the project for sure feels rejuvenating. It improved the quality of the code, but it also made our work easier and more efficient. It's hard to measure the impact of these changes on the build time because the code changed drastically in some places, but I believe that it has improved in some way (at least for the build from Xcode, because it doesn't need to display 400 warnings anymore). If it's not the build time itself, the Xcode UI alone for sure seems faster for the above reason.
Still, build time optimization is the other big topic we tackled once the warnings got resolved, and I'll write about it in detail in the next post. And back to warnings, we are now able to automatically control build warnings count and ensure we don't introduce any new warnings to the master branch. I'll cover it too in the following posts, stay tuned for more!