Saturday, January 27, 2024
HomeiOS DevelopmentWriting code that makes errors more durable – Donny Wals

Writing code that makes errors more durable – Donny Wals


As we work on initiatives, we often add extra code than we take away. Not less than that’s how issues are initially of our undertaking. Whereas our undertaking grows, the wants of the codebase change, and we begin refactoring issues. One factor that’s usually fairly exhausting to get precisely proper when coding is the sorts of abstractions and design patterns we truly want. On this submit, 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 a number of situations during which you may end up questioning what to do. And even worse, situations the place you begin noticing that some issues go flawed generally, 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 forestall ourselves from writing code that’s vulnerable to containing errors.

Frequent errors in codebases

Whenever 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 submit, you’ll usually see that the codebase accommodates code duplication, plenty of if statements, some swap 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, swap, 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 won’t be the error; the code permits you as a developer to make errors extra simply when it’s not structured and designed to forestall errors.

Let’s check out some examples of how errors might be made too straightforward by means of code.

Errors because 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.consumer.givenName) (viewModel.consumer.familyName) ((viewModel.consumer.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 consumer we seize a number of view mannequin properties and we format them properly for our consumer.

As soon as the app that accommodates this view grows, we would have to seize the identical information from a (totally different) view mannequin, and format it an identical 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 information a technique, and one other view presents information in one other manner.

You may replace this view and consider mannequin as follows to repair the potential for errors:

class MyViewModel: ObservableObject {
  // ...

  var formattedUsername: String {
    return "(consumer.givenName) (consumer.familyName) ((consumer.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 title.

It might be even higher if we moved the formatted title 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.consumer.formattedUsername)
  }
}

Whereas this code permits us to simply get a formatted username wherever now we have entry to a consumer, we’re violating a precept referred to as the Regulation of Demeter. I’ve written about this earlier than in a submit the place I speak about free 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 okay. Nevertheless, by accessing consumer.formattedUsername on this view mannequin, our view additionally has an implicit dependency on Person. And never simply that, it additionally relies on view mannequin gaining access to a consumer object.

I’d want to make another 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 consumer.formattedUsername
  }
}
struct MyView {
  @ObservedObject var viewModel: MyViewModel

  var physique: some View {
    Textual content(viewModel.formattedUsername)
  }
}

This may really feel a bit redundant at first however when you begin listening to preserving your implicit dependencies in test and also you attempt to solely entry properties on the article you rely upon with out chaining a number of accesses you’ll discover that making adjustments to your code all of a sudden requires much less work than it does when you could have implicit dependencies far and wide.

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 selected manner.

If there’s a couple of place that ought to current this button, I might copy and paste it and issues will likely be wonderful.

Nevertheless, a number of months later we would have to make the button labels daring as a substitute of normal font weight and will probably be manner too straightforward to overlook one or two buttons that we forgot about. We might do a full undertaking seek for Button however that may probably yield far more outcomes than simply the buttons that we wish to change. This makes it far too straightforward to miss a number of buttons that we ought to be updating.

Duplicating code or logic a few times often isn’t an enormous deal. Actually, generally generalizing or inserting the duplicated code someplace is extra tedious and complicated than it’s price. Nevertheless, when you begin to duplicate increasingly, or once you’re duplicating issues which might be important to maintain in sync, it’s best to think about making a small and light-weight abstraction or wrapper to forestall errors.

Stopping errors associated to code duplication

At any time when you end up reaching for cmd+c in your keyboard, it’s best to ask your self whether or not you’re about to repeat one thing that may 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 acquire extra expertise within the area you’ll develop a way for when issues are vulnerable to duplication and candidate to summary.

Particularly when an abstraction might 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 downside by ensuring that we thought of the best stage of inserting our consumer’s formatted title. Initially we put it on the view mannequin, however then we modified this by giving the consumer itself a formatted title. Permitting anyplace that has entry to our consumer object to seize a formatted title.

An additional benefit right here is we maintain our view mannequin as skinny as doable, and we’ve made our consumer 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 jot down a customized button type if that higher suits your use case.

Errors because of complicated state

Managing state is difficult. I don’t belief anyone that may argue in any other case.

It’s not unusual for code to slowly however certainly 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. Usually 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 foul state in any other case.

My favourite instance of a scenario 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) { information, response, error in
  guard error == nil else {
    // one thing went flawed, deal with error
    return
  }

  guard let information, let response else {
    // one thing went VERY flawed
    // now we have 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 certainly rising the app and altering the necessities.

After we design our code, we are able to repair these sorts of issues earlier than they happen. Whenever you discover that you may specific an unimaginable state in your app resulting from a progress in variables which might be meant to work together collectively, think about leveraging enums to signify the states your app might be in.

That manner, you considerably decrease your probabilities 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 End result 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 because of not figuring out 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 selected order to guarantee that all the things works accurately, and all bookkeeping is carried out accurately.

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.

Whenever you add a baby view controller you write code that appears a bit 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 seems to be 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 accurately may end up in 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 occasionally to recollect.

To repair this, the API design might be improved to mechanically name the right methodology 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 accurately 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, generally there’s a good cause why an API was designed the way in which it was. I believe that is the case for Apple’s view controller containment APIs for instance. We’re presupposed to arrange the kid view controller’s view between the calls we’re presupposed to make. However nonetheless… the API might certainly be reworked to make making errors more durable.

Designing code that helps stopping errors

Whenever you’re writing code it’s best to at all times be looking out for anti-patterns like copy-pasting code so much, having plenty of complicated state that permits 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 acquire 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 because of this you spent loads of time enthusiastic about the way you wish to name sure bits of code.

At any time when I’m engaged on a brand new characteristic, I have a tendency to jot down my “name web site” fist. The decision web site 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 presupposed to render an inventory of things which might be fetched from varied sources I’ll most likely write one thing like:

Record(itemSource.allItems) { merchandise in 
  // ...
}

After all, that code won’t work but however I’ll know what to goal for. Regardless of what number of information sources I find yourself with, I would like my Record to be straightforward to make use of.

This methodology of writing code by figuring out how I wish to use it first might be utilized to each layer of your codebase. Generally it’s going to work very well, different instances you’ll discover that it is advisable deviate out of your “superb” name web site but it surely helps concentrate on what issues; ensuring the code is straightforward to make use of.

At any time when I’m designing APIs I take into consideration this submit from Dave DeLong.

Particularly, this quote at all times stands out to me:

A terrific API is form to all builders who work with it.

Each methodology 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 exhausting (or ideally, unimaginable) to misuse that API in addition to having good error messages and failure modes.

Shifting on from API design, in the event you’re modeling state that largely revolves round a number of booleans, think about enums as a substitute. Even in the event you’re modeling one thing like whether or not or not a view ought to animate, an enum may also help you make your code extra readable and maintainable in the long term.

Greater than something, in the event you assume {that a} sure little bit of code feels “off”, “too complicated” or “not fairly proper”, there’s probability your instinct is appropriate. Our code ought to be as simple to know as doable. So every time we really feel like we’re doing the other, we must always appropriate that.

That’s to not say that every one complicated code is dangerous. Or that every one repetition is dangerous. And even that each little bit of complicated state ought to turn into an enum. These are all simply flags that ought to stand out to you as one thing that it’s best to take note of. Any time you possibly can change your code a bit with a view to make it unimaginable to signify an unimaginable state, or if you can also make some adjustments to your code that guarantee you possibly can’t move dangerous arguments to a way, that’s a win.

In Abstract

Writing good code might be actually exhausting. On this submit, I outlined a few examples of code that permits builders to make errors. There are various 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 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 manner that ensures our code stays protected and simple to make use of.

General, state is the toughest factor to handle in my expertise. Modeling state in a manner that permits us to signify complicated 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 title and related values as a substitute.

What are some widespread coding errors that you’ve discovered to determine alongside the way in which? I’d love in the event you advised me all about them on X or Threads.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments