As we work on initiatives, we often add extra code than we take away. A minimum of that’s how issues are firstly of our mission. Whereas our mission grows, the wants of the codebase change, and we begin refactoring issues. One factor that’s typically fairly arduous to get precisely proper when coding is the sorts of abstractions and design patterns we really want. On this publish, I wish to discover a mechanism that I wish to leverage to ensure my code is strong with out really worrying an excessive amount of about abstractions and design patterns within the first place.
We’ll begin off by sketching just a few eventualities wherein you may end up questioning what to do. And even worse, eventualities the place you begin noticing that some issues go fallacious generally, on some screens. After that, we’ll have a look at how we will leverage Swift’s kind system and entry management to stop ourselves from writing code that’s vulnerable to containing errors.
Widespread errors in codebases
If you have a look at codebases which have grown over time with out making use of the rules that I’d like to stipulate on this publish, you’ll typically see that the codebase accommodates code duplication, a number of if
statements, some change
statements right here and there, and an entire bunch of mutable values.
None of those are errors on their very own, I might by no means, ever argue that the existence of an if
assertion, change
, and even code duplication is a mistake that ought to instantly be rectified.
What I am saying is that these are sometimes signs of a codebase the place it turns into simpler and simpler over time to make errors. There’s an enormous distinction there. The code itself may not be the error; the code permits you as a developer to make errors extra simply when it’s not structured and designed to stop errors.
Let’s check out some examples of how errors could be made too simple via code.
Errors on account of code duplication
For instance, think about having a SwiftUI view that appears as follows:
struct MyView {
@ObservedObject var viewModel: MyViewModel
var physique: some View {
Textual content("(viewModel.person.givenName) (viewModel.person.familyName) ((viewModel.person.e mail))")
}
}
By itself, this doesn’t look too dangerous. We simply have a view, and a view mannequin, and to current one thing to the person we seize just a few view mannequin properties and we format them properly for our person.
As soon as the app that accommodates this view grows, we would must seize the identical information from a (totally different) view mannequin, and format it equivalent to the way it’s formatted in different views.
Initially some copying and pasting will minimize it however in some unspecified time in the future you’ll often discover that issues get out of sync. One view presents information a method, and one other view presents information in one other means.
You possibly can replace this view and look at mannequin as follows to repair the potential for errors:
class MyViewModel: ObservableObject {
// ...
var formattedUsername: String {
return "(person.givenName) (person.familyName) ((person.e mail))"
}
}
struct MyView {
@ObservedObject var viewModel: MyViewModel
var physique: some View {
Textual content(viewModel.formattedUsername)
}
}
With this code in place, we will use this view mannequin in a number of locations and reuse the formatted identify.
It could be even higher if we moved the formatted identify onto our Person object:
extension Person {
// ...
var formattedUsername: String {
return "(givenName) (familyName) ((e mail))"
}
}
struct MyView {
@ObservedObject var viewModel: MyViewModel
var physique: some View {
Textual content(viewModel.person.formattedUsername)
}
}
Whereas this code permits us to simply get a formatted username wherever we’ve got entry to a person, we’re violating a precept known as the Legislation of Demeter. I’ve written about this earlier than in a publish the place I discuss unfastened coupling so I received’t go too in depth for now however the important thing level to recollect is that our view explicitly relies on MyViewModel
which is ok. Nonetheless, by accessing person.formattedUsername
on this view mannequin, our view additionally has an implicit dependency on Person
. And never simply that, it additionally relies on view mannequin accessing a person object.
I’d favor to make yet one more change to this code and make it work as follows:
extension Person {
// ...
var formattedUsername: String {
return "(givenName) (familyName) ((e mail))"
}
}
class MyViewModel: ObservableObject {
// ...
var formattedUsername: String {
return person.formattedUsername
}
}
struct MyView {
@ObservedObject var viewModel: MyViewModel
var physique: some View {
Textual content(viewModel.formattedUsername)
}
}
This may really feel slightly redundant at first however when you begin being attentive to preserving your implicit dependencies in verify and also you attempt to solely entry properties on the article you rely on with out chaining a number of accesses you’ll discover that making modifications to your code instantly requires much less work than it does when you might have implicit dependencies in every single place.
One other type of code duplication can occur once you’re styling UI components. For instance, you may need written some code that types a button in a specific means.
If there’s multiple place that ought to current this button, I might copy and paste it and issues will likely be wonderful.
Nonetheless, just a few months later we would must make the button labels daring as a substitute of normal font weight and will probably be means too simple to overlook one or two buttons that we forgot about. We might do a full mission seek for Button
however that may most probably yield far more outcomes than simply the buttons that we wish to change. This makes it far too simple to miss a number of buttons that we needs to be updating.
Duplicating code or logic a couple of times often isn’t an enormous deal. Actually, generally generalizing or inserting the duplicated code someplace is extra tedious and sophisticated than it’s value. Nonetheless, when you begin to duplicate increasingly, or once you’re duplicating issues which can be important to maintain in sync, it is best to contemplate making a small and light-weight abstraction or wrapper to stop errors.
Stopping errors associated to code duplication
Every time you end up reaching for cmd+c
in your keyboard, it is best to ask your self whether or not you’re about to repeat one thing that can have to be copied typically. Since none of us have the flexibility to reliably predict the long run, this may at all times be considerably of a guess. As you achieve extra expertise within the discipline you’ll develop a way for when issues are vulnerable to duplication and an excellent candidate to summary.
Particularly when an abstraction could be added in a easy method you shouldn’t have a really excessive tolerance for copying and pasting code.
Think about the view mannequin instance from earlier. We had been capable of resolve our drawback by ensuring that we thought of the precise degree of inserting our person’s formatted identify. Initially we put it on the view mannequin, however then we modified this by giving the person itself a formatted identify. Permitting anywhere that has entry to our person object to seize a formatted identify.
An additional advantage right here is we maintain our view mannequin as skinny as doable, and we’ve made our person object extra versatile.
Within the case of a button that should seem in a number of locations it is sensible to wrap the button in a customized view. It might additionally make sense to put in writing a customized button type if that higher matches your use case.
Errors on account of complicated state
Managing state is tough. I don’t belief anyone that may argue in any other case.
It’s not unusual for code to slowly however absolutely flip into a posh state machine that makes use of a handful of boolean values and a few strings to find out what the app’s present state actually is. Typically the result’s that when as soon as boolean is true, a few others should be false as a result of this system could be in a nasty state in any other case.
My favourite instance of a state of affairs the place we’ve got a number of bits of state together with some guidelines about when this state is or isn’t legitimate is URLSession
‘s callback for an information job:
URLSession.shared.dataTask(with: url) { information, response, error in
guard error == nil else {
// one thing went fallacious, deal with error
return
}
guard let information, let response else {
// one thing went VERY fallacious
// we've got no error, no information, and no response
return
}
// use information and response
}
If our request fails and comes again as an error
, we all know that the response
and information
arguments have to be nil
and vice-versa. This can be a easy instance however I’ve seen a lot worse in code I’ve labored on. And the issue was by no means launched knowingly. It’s at all times the results of slowly however absolutely rising the app and altering the necessities.
Once we design our code, we will repair these sorts of issues earlier than they happen. If you discover you can specific an not possible state in your app resulting from a development in variables which can be supposed to work together collectively, contemplate leveraging enums to signify the states your app could be in.
That means, you considerably decrease your possibilities of writing incorrect state into your app, which your customers will take pleasure in.
For instance, Apple might have improved their URLSession
instance with the Outcome
kind for callbacks. Fortunately, with async / await dangerous state can’t be represented anymore as a result of a information
name now returns a non-optional Knowledge
and URLResponse
or throws an Error
.
Errors on account of not realizing the magical incantation
One final instance that I’d like to focus on is when codebases require you to name a sequence of strategies in a specific order to be sure that the whole lot works appropriately, and all bookkeeping is carried out appropriately.
That is often the results of API design that’s considerably missing in its usability.
One instance of that is the API for including and eradicating baby view controllers in UIKit.
If you add a baby view controller you write code that appears slightly like this:
addChild(childViewController)
// ... some setup code ...
childViewController.didMove(toParent: self)
That doesn’t appear too dangerous, proper.
The syntax for eradicating a baby view controller appears to be like as follows:
childViewController.willMove(toParent: nil)
// ... some setup code ...
childViewController.removeFromParent()
The distinction right here is whether or not we name willMove
or didMove
on our childViewController
. Not calling these strategies appropriately can lead to too few or too many view controller lifecycle occasions being despatched to your baby view controller. Personally, I at all times neglect whether or not I must name didMove
or willMove
after I work with baby view controllers as a result of I do it too sometimes to recollect.
To repair this, the API design may very well be improved to mechanically name the right technique once you make a name to addChild
or removeFromParent
.
In your personal API design, you’ll wish to look out for conditions the place your program solely works appropriately once you name the precise strategies in the precise order. Particularly when the tactic calls ought to at all times be grouped intently collectively.
That stated, generally there’s a good motive why an API was designed the best way it was. I feel that is the case for Apple’s view controller containment APIs for instance. We’re speculated to arrange the kid view controller’s view between the calls we’re speculated to make. However nonetheless… the API might absolutely be reworked to make making errors tougher.
Designing code that helps stopping errors
If you’re writing code it is best to at all times be looking out for anti-patterns like copy-pasting code rather a lot, having a number of complicated state that enables for incorrect states to be represented, or once you’re writing code that has very particular necessities relating to the way it’s used.
As time goes on and also you achieve increasingly coding expertise, you’ll discover that it will get simpler and simpler to identify potential pitfalls, and you can begin getting forward of them by fixing issues earlier than they exist.
Normally which means that you spent lots of time occupied with the way you wish to name sure bits of code.
Every time I’m engaged on a brand new characteristic, I have a tendency to put in writing my “name website” fist. The decision website means the half the place I work together with the characteristic code that I’m about to put in writing.
For instance, if I’m constructing a SwiftUI view that’s speculated to render a listing of things which can be fetched from varied sources I’ll most likely write one thing like:
Listing(itemSource.allItems) { merchandise in
// ...
}
After all, that code may not work but however I’ll know what to intention for. Regardless of what number of information sources I find yourself with, I would like my Listing
to be simple to make use of.
This technique of writing code by figuring out how I wish to use it first could be utilized to each layer of your codebase. Typically it can work very well, different instances you’ll discover that it’s good to deviate out of your “perfect” name website nevertheless it helps deal with what issues; ensuring the code is simple to make use of.
Every time I’m designing APIs I take into consideration this publish from Dave DeLong.
Particularly, this quote at all times stands out to me:
An ideal API is form to all builders who work with it.
Each technique you write and each class you design has an API. And it’s a good suggestion to be sure that this API is pleasant to make use of. This contains ensuring that it’s arduous (or ideally, not possible) to misuse that API in addition to having good error messages and failure modes.
Shifting on from API design, should you’re modeling state that principally revolves round a number of booleans, contemplate enums as a substitute. Even should you’re modeling one thing like whether or not or not a view ought to animate, an enum will help you make your code extra readable and maintainable in the long term.
Greater than something, should you suppose {that a} sure little bit of code feels “off”, “too complicated” or “not fairly proper”, there’s an excellent probability your instinct is right. Our code ought to be as simple to grasp as doable. So every time we really feel like we’re doing the other, we must always right that.
That’s to not say that each one complicated code is dangerous. Or that each one repetition is dangerous. And even that each little bit of complicated state ought to grow to be an enum. These are all simply flags that ought to stand out to you as one thing that it is best to take note of. Any time you possibly can change your code a bit with a view to make it not possible to signify an not possible state, or if you can also make some modifications to your code that guarantee you possibly can’t cross dangerous arguments to a technique, that’s a win.
In Abstract
Writing good code could be actually arduous. On this publish, I outlined a few examples of code that enables builders to make errors. There are lots of ways in which code can open a developer as much as errors, and these often contain code that has advanced over time, which might imply that blind spots have crept into the codebase with out the developer noticing.
By expertise, we will be taught to determine our blind spots early and we will defensively write code that anticipates change in a means that ensures our code stays secure and straightforward to make use of.
Total, state is the toughest factor to handle in my expertise. Modeling state in a means that enables us to signify complicated states in a secure method is extraordinarily helpful. Subsequent time you are contemplating writing an ‘if’ assertion that compares two or extra values to find out what ought to occur, contemplate writing an enum with a descriptive identify and related values as a substitute.
What are some widespread coding errors that you’ve got discovered to determine alongside the best way? I’d love should you instructed me all about them on X or Threads.