As we work on tasks, we often add extra code than we take away. At the very least that’s how issues are in the beginning of our challenge. Whereas our challenge grows, the wants of the codebase change, and we begin refactoring issues. One factor that’s usually fairly arduous to get precisely proper when coding is the sorts of abstractions and design patterns we truly want. On this put up, I want to discover a mechanism that I wish to leverage to verify my code is powerful with out truly 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 would possibly end up questioning what to do. And even worse, eventualities the place you begin noticing that some issues go flawed typically, on some screens. After that, we’ll take a look at how we are able to leverage Swift’s kind system and entry management to stop ourselves from writing code that’s susceptible to containing errors.
Widespread errors in codebases
If you take a look at codebases which have grown over time with out making use of the ideas that I’d like to stipulate on this put up, you’ll usually see that the codebase comprises code duplication, numerous if
statements, some change
statements right here and there, and a complete bunch of mutable values.
None of those are errors on their very own, I’d 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 a giant 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 will be made too simple by way of 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 unhealthy. 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 comprises this view grows, we would have to seize the identical knowledge from a (completely 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 sooner or later you’ll often discover that issues get out of sync. One view presents knowledge a technique, and one other view presents knowledge in one other means.
You might replace this view and consider 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 are able to 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 Consumer object:
extension Consumer {
// ...
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 now we have entry to a person, we’re violating a precept referred to as the Regulation of Demeter. I’ve written about this earlier than in a put up the place I discuss free coupling so I received’t go too in depth for now however the important thing level to recollect is that our view explicitly is dependent upon MyViewModel
which is ok. Nevertheless, by accessing person.formattedUsername
on this view mannequin, our view additionally has an implicit dependency on Consumer
. And never simply that, it additionally is dependent upon view mannequin gaining access to a person object.
I’d favor to make yet one more change to this code and make it work as follows:
extension Consumer {
// ...
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 would possibly really feel a little bit redundant at first however when you begin being attentive to preserving your implicit dependencies in test and also you attempt to solely entry properties on the thing you depend upon with out chaining a number of accesses you’ll discover that making modifications to your code all of the sudden requires much less work than it does when you’ve got implicit dependencies in every single place.
One other type of code duplication can occur once you’re styling UI parts. 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 might be superb.
Nevertheless, just a few months later we would have to make the button labels daring as a substitute of standard font weight and will probably be means too simple to overlook one or two buttons that we forgot about. We might do a full challenge seek for Button
however that might most definitely yield far more outcomes than simply the buttons that we need 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 few times often isn’t a giant deal. The truth is, typically generalizing or inserting the duplicated code someplace is extra tedious and complicated than it’s value. Nevertheless, when you begin to duplicate increasingly more, or once you’re duplicating issues which might be important to maintain in sync, you need to think about 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, you need to ask your self whether or not you’re about to repeat one thing that can have to be copied usually. Since none of us have the flexibility to reliably predict the longer term, it will 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 susceptible to duplication and a very good candidate to summary.
Particularly when an abstraction will 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 in a position to resolve our drawback by ensuring that we thought of the best 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 preserve our view mannequin as skinny as potential, 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 smart to wrap the button in a customized view. It might additionally make sense to jot down a customized button type if that higher suits your use case.
Errors on account of advanced state
Managing state is tough. I don’t belief anyone that might argue in any other case.
It’s not unusual for code to slowly however absolutely flip into a fancy 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. Usually the result’s that when as soon as boolean is true, a few others should be false as a result of this system can be in a foul state in any other case.
My favourite instance of a state of affairs the place now we have a number of bits of state together with some guidelines about when this state is or isn’t legitimate is URLSession
‘s callback for a knowledge activity:
URLSession.shared.dataTask(with: url) { knowledge, response, error in
guard error == nil else {
// one thing went flawed, deal with error
return
}
guard let knowledge, let response else {
// one thing went VERY flawed
// now we have no error, no knowledge, and no response
return
}
// use knowledge and response
}
If our request fails and comes again as an error
, we all know that the response
and knowledge
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 are able to repair these sorts of issues earlier than they happen. If you discover that you would be able to categorical an unimaginable state in your app because of a development in variables which might be supposed to work together collectively, think about leveraging enums to signify the states your app will be in.
That means, you considerably decrease your probabilities of writing incorrect state into your app, which your customers will get pleasure from.
For instance, Apple might have improved their URLSession
instance with the Outcome
kind for callbacks. Fortunately, with async / await unhealthy state can’t be represented anymore as a result of a knowledge
name now returns a non-optional Information
and URLResponse
or throws an Error
.
Errors on account of not understanding the magical incantation
One final instance that I’d like to spotlight is when codebases require you to name a sequence of strategies in a specific order to guarantee that every thing 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 a little bit like this:
addChild(childViewController)
// ... some setup code ...
childViewController.didMove(toParent: self)
That doesn’t appear too unhealthy, 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 have to name didMove
or willMove
once I work with baby view controllers as a result of I do it too sometimes to recollect.
To repair this, the API design could possibly be improved to routinely name the right technique once you make a name to addChild
or removeFromParent
.
In your personal API design, you’ll need to look out for conditions the place your program solely works appropriately once you name the best strategies in the best order. Particularly when the strategy calls ought to at all times be grouped carefully collectively.
That mentioned, typically there’s a good cause 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 imagined to arrange the kid view controller’s view between the calls we’re imagined 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 you need to at all times be looking out for anti-patterns like copy-pasting code loads, having numerous advanced 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 more 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 because of this you spent plenty of time interested by the way you need to name sure bits of code.
Every time I’m engaged on a brand new characteristic, I have a tendency to jot down my “name website” fist. The decision website means the half the place I work together with the characteristic code that I’m about to jot down.
For instance, if I’m constructing a SwiftUI view that’s imagined to render an inventory of things which might be fetched from varied sources I’ll most likely write one thing like:
Checklist(itemSource.allItems) { merchandise in
// ...
}
After all, that code may not work but however I’ll know what to goal for. Irrespective of what number of knowledge sources I find yourself with, I would like my Checklist
to be simple to make use of.
This technique of writing code by figuring out how I need to use it first will be utilized to each layer of your codebase. Typically it is going to work very well, different occasions you’ll discover that you have to deviate out of your “very best” name website nevertheless it helps concentrate on what issues; ensuring the code is straightforward to make use of.
Every time I’m designing APIs I take into consideration this put up from Dave DeLong.
Specifically, this quote at all times stands out to me:
A fantastic API is variety 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 guarantee that this API is pleasant to make use of. This consists of ensuring that it’s arduous (or ideally, unimaginable) to misuse that API in addition to having good error messages and failure modes.
Transferring on from API design, if you happen to’re modeling state that principally revolves round a number of booleans, think about enums as a substitute. Even if you happen to’re modeling one thing like whether or not or not a view ought to animate, an enum might help you make your code extra readable and maintainable in the long term.
Greater than something, if you happen to assume {that a} sure little bit of code feels “off”, “too advanced” or “not fairly proper”, there’s a very good likelihood your instinct is right. Our code ought to be as easy to know as potential. So each time we really feel like we’re doing the alternative, we should always right that.
That’s to not say that each one advanced code is unhealthy. Or that each one repetition is unhealthy. And even that each little bit of advanced state ought to turn into an enum. These are all simply flags that ought to stand out to you as one thing that you need to take note of. Any time you may change your code a bit in an effort to make it unimaginable to signify an unimaginable state, or if you can also make some modifications to your code that guarantee you may’t go unhealthy arguments to a way, that’s a win.
In Abstract
Writing good code will be actually arduous. On this put up, I outlined a few examples of code that enables builders to make errors. There are a lot of ways in which code can open a developer as much as errors, and these often contain code that has developed over time, which might imply that blind spots have crept into the codebase with out the developer noticing.
By way of expertise, we are able to study to determine our blind spots early and we are able to defensively write code that anticipates change in a means that ensures our code stays protected and straightforward to make use of.
General, state is the toughest factor to handle in my expertise. Modeling state in a means that enables us to signify advanced states in a protected 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, think about writing an enum with a descriptive identify and related values as a substitute.
What are some frequent coding errors that you’ve realized to determine alongside the best way? I’d love if you happen to advised me all about them on X or Threads.