Category: iOS Development

When is it *good* for your App to crash?

Most of the time, a crashing app is the very last thing you want. Obviously.

But sometimes you really do want your app to crash to enforce correct usage of your APIs.

What’s worse than a crash?

Crashes are obviously bad – it’s disruptive to your user, they might lose data or work in progress, it makes them feel frustrated and less likely to open your app again, and it just smells like bad quality.

But there’s something worse than a crash: undefined behaviour.

Our programs are carefully thought through, and crafted so that the right input data is manipulated, used for calculations, stored, outputted, and otherwise trusted. But this also makes them brittle. Whether you realize it or not, all of these complex behaviours only work when you constrain your code to be used with some predefined, known range of inputs.

When your code is given unexpected types of data, you can’t control what will happen – it’s outside of what you designed and tested for. You could end up accidentally corrupting user data, opening security holes, or (the most likely) providing inaccurate and confusing information to the user.

In some cases, these things are worse than crashing.

  • What if your banking app tells the user they have more money than they really have and then they suddenly can’t pay rent!
  • What if your app ends up in a state where none of the buttons do anything and you can’t navigate out of that screen?

There are of course others, but these user experiences might actually be *worse* than crashing. So crash! The user knows something went wrong, and needs to restart the app to fix it.

Crashing as a Development Tool

Crashing is also extremely useful in teaching developers how to use your APIs.

A (contrived) example

Lets say you’re writing the official calculator app for a university. It’s very important that it do math correctly, because all of this university’s students will be forced to use this app for all of their exams.

Ok so your inputs are numbers and operators.

Let’s imagine you had some calculator UI, with logic that looks something like this:

We could write a custom UITextField subclass that only supported numbers that could be converted into a float.

class CalculatorUI {
    @IBOutlet var input1: NumericTextField!
    @IBOutlet var input2: NumericTextField!
    @IBOutlet var result: NumericTextField!
    
    /// Takes the inputs, adds them, and updates the result TextField.
    @IBAction func didPressAddButton() {
        result.floatValue = input1.floatValue + input2.floatValue
    }
}

This super clean, simple class relies on pushing some of the complication into the NumericTextField:

// Basic Implementation
class NumericTextField: UITextField {

    var floatValue: Float {
        get {
            if let acceptableValue = self.text as? Float {
                return acceptableValue
            } else {
                // What do we return here?
                return 0
            }
        }
        set { self.text = "\(newValue)" }
    }
}

There are a couple problems with our basic implementation:

  1. We always return 0 if the text can’t be converted into a float. If a student somehow types non-float values like "two" and "two" as their inputs, they might not realize this isn’t supported, and actually believe that their answer is 0! This answer is not correct! AHH
  2. The NumericTextField class design encourages misuse! There is nothing preventing us from trying to access the .text property directly, but the whole point of this custom text field is to restrict the types to be floats.

Clearly, the real solution here is to show some error message that tells the user to only type numbers, or better yet to use a numeric keyboard style so enforce that they can’t enter alphabetical characters.

But we still want to improve this text field’s implementation.

Crashing is a hint to the developer!

The key thing here is that both of the problems described above are developer errors. They are misusing the API. We want to catch these mistakes as early into the development process as possible. And the best way to call attention to them is to crash.

Yep. We’re going to crash. On purpose.

Solution #1: Catch the invalid text field values

If the text field’s getter method is unable to convert the text to a Float, that is because the developer has allowed the user to input letters.

class NumericTextField: UITextField {

    var floatValue: Float {
        get {
            guard let acceptableValue = self.text as? Float else {
                fatalError("The NumericTextField assumes that is always has a text value that can be converted to a Float.")
            }
                
            return acceptableValue
        }
        set { self.text = "\(newValue)" }
    }
}

Using Swift’s fatalError lets us supply a crash message, which is much clearer than enforcing this behaviour via a force-unwrap:

class NumericTextField: UITextField {

    var floatValue: Float {
        get {
            // BAD!!  Don't use ! (force-unwrap)
            return self.text as! Float
        }
        set { self.text = "\(newValue)" }
    }
}

Solution #2: Prevent usage of the superclass’s API

The second thing we’ll prevent is usage of the UITextField.text property.

We can do this by overriding it and crashing.

class NumericTextField: UITextField {
    
    override var text: String? {
        set { fatalError("Use `floatValue` instead.") }
        get { fatalError("Use `floatValue` instead.") }
    }

    var floatValue: Float {
        get {
            guard let acceptableValue = super.text as? Float else {
                fatalError("Our error message...")
            }
                
            return acceptableValue
        }
        set { super.text = "\(newValue)" }
    }
}

Notice that we’ve also changed the floatValue getter and setter to use the .text property from the super class rather than self, since calling self.text will now crash.

We’ve now made it clear to the developer (or our future self) what the allowable usages of our class are.

Crashing in development is better than undefined behaviour in production.

With these two changes, we’ve made is easier for developers to use our API correctly.

This isn’t something you want to do on all of your APIs. You don’t want to allow anything external (such as user input or server responses) to be able to crash your app.

But the case above shows how you can use crashing as a tool to make sure that your code is using your other code correctly.

Why Agile-at-Scale is So Painful for Developers

Does you use the Agile methodology at work? What do you think of it? Do you think it helps you be more productive?

I suspect your answer to that question depends on a number of factors, but the key determiners are likely the role assigned to you by the Agile framework, and the details of how Agile has been adopted by your company.

Why Agile is Supposedly Better Than Waterfall

Waterfall has a fatal flaw – although thorough planning and a long-term roadmap are useful in organizing a coherent project plan and achieving a big goal, you’re stuck with this plan once you’ve started. Projects planned with waterfall will often do huge portions of their work before they are able to grab the separate pieces of a system and try them out together.

This means that if something is wrong, you don’t find out for a long time – possibly until its too late.

And if the market changes while you’re working on this project, well that’s just too bad. You’ve already committed to making the thing you planned from the beginning. At this point you have no choice but to see it through and release your too-late product to the already-moved-on market.

Agile is primarily intended to solve one key problem in Waterfall; just as the name describes, it gives businesses the ability to change their mind quickly, and pivot to a new plan.

It accomplishes this by prescribing that work be chunked into 2-week commitments (this can vary from 1 to 4 weeks). At the end of that 2 weeks, the entire software system should be in a working (shippable) state, and everyone should be able to choose the next most important thing they can get done in the next two weeks.

This is an elegant idea. And it works for some teams, but it requires a lot of discipline, and it doesn’t scale well.

Agile for really large teams – sucks.

What Is Hard About Scaling Agile

There is a whole industry that has arisen around trying to apply the Agile methodology to large corporations. And this is a noble pursuit. It is easy to see why corporations would want to adopt these paradigms – all the little guys do it with great success, why shouldn’t they!

But agile is inherently challenging for large teams. Imagine a team of 1000 – this breaks down to at least 100 scrum teams if you keep a maximum of 10 people per team.

The projects endeavoured by these types of teams are enormous, and require detailed planning and coordination on multiple levels.

How do you simultaneously get a team of 1000 working together in the same direction on these projects, but then also be able to change the direction of the company at a week’s notice?

How do you simultaneously empower 1000 employees to feel ownership for their work while retaining the ability to change what they work on at a week’s notice?

The answer is, of course, you can’t. You need to compromise somewhere, and so these frameworks for applying Agile principles at a large scale do compromise.

Instead of being able to change the plan every couple weeks, you group sprints into sets, and re-evaluate at the end of that couple-of-months period instead. You play mini-Waterfall.

And to make sure everyone at the business level stays up-to-date on how things are progressing, you have hoards of personnel dedicated to communicating status between teams.

And this is where scaling Agile breaks down. When you introduce this much planning and coordination into agile, you lose the agility.

It’s like Heisenberg’s uncertainty principle – trying to measure a thing causes a change in that thing, and you’ve kind of ruined the accuracy of the measurement.

How your role in large-scale agile defines whether you think it’s working:

This is where we can see that your feelings on working within an Agile system will depend on your position with that system.

If you are an individual contributor, you will now spend a large portion of your time allowing others to measure you. It will go far beyond your daily standup. Every project or initiative in the company will have its own meetings to both plan and assess progress, and you will be expected to attend lots of these.

If you are a communicator in this system, you are incentivized to ask lots of questions, and have a clear picture of everything that’s going on at all times. But this incentive works against the pure productivity of the team. Every status update you give is an interruption from whatever you were working on. And each interruption takes a long time to recover from. Focus is not just lost for 15 seconds while you have a “quick check-in” – it is lost for half an hour while the you struggle to re-enter “the zone” and re-establish the deep concentration they were in.

If you are a leader or executive in this system, you are too far away from these interruptions to see their devastation first-hand. Without real proximity to the scrum teams or experience working as a contributor, you may not realize that the your effort to stay abreast of status in an attempt to make informed decisions has introduced a counter-productive vector in the system.

The need to keep executives appraised is important and fair – the business does absolutely need to understand what’s going on in order to make the best decisions about how to allocate money and resources.

But it’s also what sucks about working as an individual contributor at giant companies.

So What?

It helps to understand these things to make them less painful. Agile doesn’t have to be bad.

It is up to the implementers to avoid imposing onerous bean-counting requirements, and to protect the developers from as much of the structure of the scaling up as possible. Sprint planning is still useful! But implementing a company-wide restriction on how to type out your task descriptions is not.

If you are a communicator, know that you are the key to pulling off agile without being a pain in the butt. Communicate effectively, but try to do as much observation from a distance as possible.

If you are an individual contributor, advocate for what you need to make yourself more productive. Remember – you are being paid (probably a lot) to use a specialized skill. Try to preserve most of your time for that job.

How to do High-Bar Code Review Without Being a Jerk

Code review is the hardest part of being a developer.

Seriously.

Whereas most of our work is between you and a computer – a machine you just have to operate correctly and indicate clear intentions to – code review is a thing between humans. And like it or not, those humans are squishy, opinionated, emotional creatures. And those traits make code review a challenge.

In principle your goal in a code review is simple: examine another person’s code and make sure their pull request (“PR”) is “good enough” to merge.

But in reality there are a gazillion other things swirling around in your brain.

  • Urgency – is this a hot fix for a major production outage?
  • Seniority – am I allowed to decline this senior developer’s PR?
  • Reaction – will someone yell at me if I decline this PR?
  • Grudges – will you decline my next PR just because I declined this one?

None of these are related to the code quality, but they are legitimate concerns – you might really rub people the wrong way if you review code unkindly.

So how do you avoid these problems? And how do you set up a team for successful code reviews?

Set the Team Up for Success

Before anyone even writes any code, set clear expectations. Anticipate the obvious problems and just avoid them.

  1. Use a code linter – many arguments in PRs are over style. And thats just not the place for it. You can easily prevent most of these disagreements from making it as far as the pull request by agreeing on a set of linter rules.
  2. Have a code style document – some things aren’t reasonably enforceable by a linter, but are still just conventions. Write these down in a central place and have everyone on the team agree to them. Getting buy-in from your team gives everyone accountability. If the team really did all agree on these rules its much easier to enforce them. All you need to do is link to that page and remind the developer that this was a team decision.
  3. Do design meetings – when starting a big task, consider having a design meeting. This might feel like a waste of time but it’s not. Having the team contribute to the design before the developer starts work will avoid large-scale issues from cropping up when it comes time to review. You don’t want to start a task over because it has a fatal design flaw. And you really don’t want to be forced into accepting a fatally flawed design because you’ve run out of time to go back and start over.
  4. Establish relationships and respect – people are more empathetic and kind when they know each other, and preferably even like each other. Invest in relationships on the team and you’ll avoid some conflicts caused by people just not taking the time to care about each other’s feelings.
  5. Write a Definition of Done – this is a formal, generic list of requirements that apply to all/most tasks. For example maybe “all code must have 70% unit test coverage, relevant documentation must be updated, the CI build must pass, and you must demo your work for your manager.” If any of these aren’t true, the PR should indicate that it is a work in progress, and it shouldn’t be allowed to be merged yet. This makes it clear the submitter is looking for early, broad feedback.

Submitting a Pull Request

Recognize that reviewing code is hard, and make it as easy as possible for your reviewers to give you useful feedback. Remember – you want them to find the problems with your PR. It saves your future self a bunch of headaches.

  1. Write a PR summary – include a brief explanation of the purpose of this PR. Link to the ticket this PR is addressing. If there’s anything odd in your solution, explain it. If there’s a reason you truly couldn’t write unit tests, give it.
  2. Add the right reviewers – don’t add your whole company. Maybe not even your whole team. Recognize that people will take time from their day to help you improve your code when you ask for their review. Just add the people who will give you the most useful feedback – perhaps the subject matter expert of the file you’re working on. Or the person who filed the bug you’re fixing. You will also likely need to add a senior developer who has the authority to accept your work.
  3. Accept the feedback – if someone provides feedback and you can’t articulate a good reason not to, just accept their suggestion. Make their requested change. This may require you to set aside a bit of the pride you have in the great work you’re submitting (and that pride is good!), but it’s not going to hurt anything to accept their suggestion, and you’ll avoid lots of unneeded conflict.

Reviewing a Pull Request

Keep these things in mind when you go to review a PR.

  1. This PR doesn’t need to be perfect. It needs to make the code better. Follow-up PRs are totally ok in many situations, as long as the current PR works correctly, and doesn’t impose an unreasonable maintenance or refactoring cost on others working in the same codebase.
  2. Start with the big picture – pull up the ticket for PR and confirm that this actually does what it’s supposed to. Does it actually work? Does it meet the Definition of Done? Is it clearly missing anything like unit tests? Provide this feedback early so that the submitted has time to rejig the whole PR if needed. There’s no sense in starting with identifying 20 typos (which the submitter may immediately start fixing) only to then suggest they take a completely different approach that will require them to throw all those fixes away anyways.
  3. Next look at the design – how have the classes been broken down? Are the components too big or have too many responsibilities? Do they define clear, testable boundaries? Provide this feedback using the verbiage of established engineering principles. “This design isn’t testable” is much less useful than “Using dependency injection here would allow you to mock out component X in your unit test”.
  4. Next review the “central class(es)” – identify the class that is the guts of the change, and start here. If there are 10 changed files in a PR, most likely only 2-3 of these contain the key logic, and the rest are helper classes or usages of the renamed API, etc. Focus your best effort on the most important classes while you still have the most energy and are providing the best feedback. Then circle back around to the other classes afterwards.
  5. Set a comment limit for yourself – It is difficult for the submitter to address 50 comments on a PR unless they are all trivial nitpicks and spelling errors. It’s also really discouraging to see that huge number of comments – it gives the impression of poor work and may really slow down the developer in future tasks.
  6. Consider in-person reviews for “bad” PRs – if there really are 50 significant flaws in the PR, take the review offline. As someone who can see these flaws, it is your responsibility to help the submitter learn. So sit down and go through the PR together, IRL. Be patient and compassionate, and don’t rush through it or make the submitter feel like they’re taking up your valuable time (even if they are). You are investing in your teammate – it will pay dividends.
  7. Consider in-person reviews for big PRs – sometimes a PR “blows up” and becomes a huge number of changes. In these cases it will likely be impossible (or unreasonably time-consuming) for a reviewer to figure out where to start. So do a group review, and have the submitter briefly present their work. The team can ask a few questions and get their bearings, and either do the full review together, or go back to separate desks and review online now that you have a better sense of what’s going on.
  8. Identify critical vs nitpicks – Be clear about which changes you think are unacceptable, and which are minor suggestions or opinions. These minor items can start with “Nit” as in “Nit: This might be clearer if you renamed this local variable to account instead of a. Also be clear if a comment is just a question, or if you’re unsure of whether your feedback is valid or not.

If They Always Make the Same “Mistakes”

Over time, you may want to give a teammate the same feedback on many PRs. This is where it becomes easy to be a jerk. Resist. Nobody wants to work with a jerk, and they certainly won’t be receptive to feedback if you treat them poorly.

  1. Remind yourself they are a professional – Nobody writes bad code on purpose. Nobody. Even that lazy sarcastic developer you don’t like for whatever reason doesn’t. So start by trying to help, and take the mindset that you are helping – not criticizing or “fixing” them.
  2. Ask yourself “is this my opinion?” – Just because you disagree with it doesn’t mean it was a mistake. Perhaps they have a valid reason for continuing with the pattern you don’t like, and perhaps that’s ok. You need to let other people win sometimes and recognize what is an opinion. There are many ways to solve all software problems, and we are constantly judging where to draw compromises. You may simply fall on opposite sides of the compromise here – if their code is functional and meets the agreements your team has set out then maybe you should just let it be and tolerate their alternative solution.
  3. Just talk about it – sometimes having a regular old human conversation about the problem will help them understand better why it even is a problem. Have the conversation at a time when they seem fresh and open to feedback, and do it in a place where they won’t feel embarrassed – don’t call them out in front of the whole team, or make them look or feel bad in front of their manager. Choose your first words carefully. “Hey I noticed something I think I can help with – do you have a second?” will be better received than “So you keep making this mistake – we need to talk about it”.
  4. Lead by example – people learn by copying each other. If you demonstrate the kind of thing you wish they were doing, they might just pick it up without you even having to confront them about it. This is also especially important if you do chat about it – make sure that you are on your extra best behaviour and demonstrate your expectations. They’ll be looking for a good example to help solidify the “right way”.

You can do it!

Reviewing code is a super important skill. It helps make your team’s code better, and makes your life better. You need to work and live with this code, so you’re making things easier for yourself by keeping a high bar in your code review. But you also need to be kind to your friends so they stay friends.

Hopefully these tips will help you be a better code reviewer! Good luck!

Clean Architecture – SOLID Design Principles Summary

Clean Architecture is a book by “Uncle Bob” Robert Martin as a followup to his popular Clean Code. It includes a brief section about the SOLID principles, which are the touchstone of his programming philosophies, and have been described in his other books.

This summary is a cheatsheet to help you remember the SOLID principles in your day-to-day work.

 

The SOLID Principles

  • Single Responsibility – each module should only have one reason to change
  • Open-Closed – Design software so its behaviour can change by adding code – not changing the existing code.
  • Liskov – Use interfaces/protocols to separate interchangeable parts
  • Interface Segregation – Dont depend on things you don’t use
  • Dependency Inversion – High-level code shouldn’t depend on low-level implementation

Auto Increment Build Number in Xcode

This is a great Copy-Paste solution for every solo iOS Developer. It automatically increases the build number of your Xcode project every time you Run your program.

Two Facts:

  1. You now have a reason to have a build number.
  2. You will spend less than a minute on this. And never worry about it again.

Go to your Project’s Build Phases tab, and click the + button. Choose New Run Script Build Phase then drag the Phase to be second (after Target Dependencies).

In the space after the shell’s /bin/bash, copy this:

<code>#!/bin/bash
bN=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
bN=$((0x$bN))
bN=$(($bN + 1))
bN=$(printf "%X" $bN)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $bN" "$INFOPLIST_FILE"</code>

 

Works like a charm. Just make sure your build number starts as “1″ and not “1.0″. This great solution was originally posted by RobertL on Stack Overflow but I liked it so much I’m sharing it!

I think this would be particularly powerful if cleverly extended to work with version control.

Powered by WordPress & Theme by Anders Norén