Let Your Code Fly
Not long ago my boss asked me to take a single feature from the iOS app I was building during the past year, and make a brand new app with only the sign-up flow, and that single feature. Easy right? Everything is in separate modules, so all I have to do is copy the relevant files and the Storyboard (UI) files into the new project and I’m done. Uhhhh… nope. I started copying only the files I thought I needed, then I began an endless chain of importing and using other parts of the codebase, until at some point I had to solve it the other way around — I duplicated the entire project, and started pealing off unnecessary things, and as you can expect, it was a tangled mess.
There is a thin line between writing code that is easy to move and light on dependencies, and writing code that is centered around a single API so that future changes will only influence a single endpoint and not multiple areas of our codebase.
We shouldn’t over engineer and we should make our code easy to delete, yet we should also avoid duplicate code, concentrate responsibility and wrap our libraries for ease of future changes, configurability, extensibility, and a bunch of other X-ity’s…
We have to always remember that a piece of code that depends on other things will end up tangled up in knots. Our code has not-so-many wings to fly, and we break one every time we import a library, use a singleton or call some FooUtils static function.
On the other hand, if we fix something and then need to search the project and make the same fix in 10 different places — our code might smell of bad duplication.
We are not building modules around being able to re-use them, but being able to change them. — “write code that is easy to delete, not easy to extend.” / programmingisterrible.com
Do we really need all of those extensions functions and utils files all over the place? Back in the day we used to have a ton of extensions with convenience functions of shortcuts to tedious APIs. Today, with Swift 3 many of those APIs became extremely concise and those utils classes and extensions might not be so dramatic anymore, sometimes adding another thread in the web that’s tying our file for a net gain of maybe 5 characters over the system API. There’s also the downside that other developers are more likely to know the official API rather than that String extension we added to the project 2 months ago…
Usually we can split libraries into 2 main types of uses:
- Specific use library, for a single place (e.g., a custom view that we need in a specific screen/page).
- General framework that is used in many places.
The 2nd kind of library is usually the scary one. When using a library for a single place, we still have the usual caveats like running code we didn’t write / don’t understand / never even tried to read. But it’s the 2nd kind of use, where we call a library from many places, each time for a small use case, that is the kind of dependency that can spread like cholera.
We should ask ourselves some serious questions before using this type of library:
- How much time will I save every time I use that library versus using the 1st party solution?
- How difficult is it to cover the edge cases (if there are any) that this library covers?
- If it’s about syntactic sugar → Does it actually make the code more readable? Or maybe it just confuses other developers who are used to the 1st party API but now need to get used to this new syntax (even though it saves a whole 30 characters every time or has this cool $ syntax!)
- And most importantly: what are the chances that I will have to move my code to a place where this library is not present, thus forcing myself to also bring that library with me? (hint — it’s a percentage with 3 digits)
Lately I have been trying to follow some simple rules to help me figure out this tension. These rules are to help us avoid both edges of the scale, aiming to write code that is easy to move around, while being easy to change and maintain:
- Never Cmd+C/Cmd+V code!: Even if you re-type a snippet, you still get the benefit of knowing exactly what’s in that snippet and the “black box” is broken.
- Always begin with private (fileprivate) extensions: make them public/global only when the need arises. It’s completely fine to have 10 UIView extensions lying around the project as long as each extension is only visible and exists in the module that uses it.
- Aim to remove 3rd party imports: Every time we import a pod, our file loses a wing. Do we really need to import SwiftDate to do 1.months when we can simply use the fabulous new Calendar and DateComponents APIs in Swift3?
- Treat syntactic sugar with a grain of salt: Remember how in the days of Objective-C we had to do a whole for-loop just to map an array? We all survived to tell the story… Every time you feel a certain line of code can become more “Swifty” with a shiny generic protocol extension — take a look at the code your Android colleagues write in Java — you will immediately feel better about how concise your line of code is.
The best part of this: we can change our mind. A good framework or utils class would emerge when we didn’t want to make them but had too much duplication and similar functionality scattered around. On the other hand, we can know for sure that a knot is misplaced when we can easily untie it by making a small, obvious and predictable change. We should be constantly writing extensions and managers with one hand while deleting all of our managers and extensions with the other.