Wrapping Primitives

Datetime:2016-08-23 02:50:01          Topic: Objective-C  Unit Testing           Share

In late 2015 I started rewriting High Rail in Swift. High Rail is an awesome app for controlling Lionel Legacy Engines , Lionel SensorTracks , and other hardware. The 1.0 code base was developed in ObjC and C. Version 2.0 is 100% Swift (with a few C functions that I have not yet converted).

Note: I remember seeing the term “Legacy Engine” years ago and said, “I don’t want the old technology; where’s the new stuff?”. Lionel uses the term “Legacy” to refer to their modern engine technology. In this context, “Legacy” means latest toy train technology.

I shared some of my ObjC to Swift experiences, thoughts and lessons learned at STL CocoaHeads earlier this year. During my presentation we discussed the idea of wrapping primitive values (e.g. Int , UInt8 , etc.) in a Swift struct to provide failable initializers and extra type safety. I don’t consider this new or groundbreaking, but it definitely makes aspects of the code easier to read, reason about, and even eliminated a bunch of “runtime required” unit tests. And Swift makes it really easy to do.

By Example

Let’s start by looking at the Engine . An Engine has a unique identifier assigned by a Lionel Legacy Base. Valid engine identifiers are 1 through 99.

Here is a first take on the Engine .

struct Engine {

    let identifier: Int

    // other details omitted
}

The Engine declares the identifier as an Int . An Int will definitely hold values 1 through 99, but doesn’t provide any compile-time safety for an out-of-bounds value.

There are about one hundred engine commands: speed, direction, quilling whistle/ horn, smoke unit control, lighting control, sound control, and many others. Each engine command requires passing the engine identifier, as an 8-bit unsigned integer, in the outgoing message.

To keep things simple, let’s look at how we might model a command for turning off an engine’s smoke unit.

Note: the following command is on over-simplification of a real command. The goal is to focus on the engine identifier aspect of the API.

Here is the “command” that turns off the Engine ’s smoke unit.

struct TurnSmokeUnitOffCommand {

    let engineID: Int

    // lots of details omitted, including command protocol adoption,
    // bit manipulation, byte buffer building, etc.
}

From an API perspective, this is not optimal. I can easily write incorrect code.

/// 187 is not a valid engine ID
let command = TurnSmokeUnitOffCommand(engineID: 187)

This is a runtime bug. Let’s see how Swift can help turn this into a compile-time error.

Making It Better

Let’s create a type-specific engine identifier named EngineID that simply wraps an Int and provides bounds checking.

struct EngineID {

    let value: Int

    init?(_ value: Int) {
        switch value {
        case 1 ... 99:
            self.value = value
        default:
            return nil
        }
    }
}

func ==(lhs: EngineID, rhs: EngineID) -> Bool {
    return lhs.value == rhs.value
}

The failable initializer guarantees the given integer value is between 1 and 99. If not, then a nil EngineID is returned. The compiler forces the caller to deal with the nil (i.e. invalid) engine identifier. We can now easily reason that a non-optional EngineID type is valid because the Swift compiler guarantees it.

Let’s update the TurnSmokeUnitOffCommand to use EngineID instead.

struct TurnSmokeUnitOffCommand {

    let engineID: EngineID

    // again, lots of details omitted, including command protocol adoption,
    // bit manipulation, byte buffer building, etc.
}

There is a lot of power in this simple change. The command (or any API using EngineID ) is now guaranteed by the compiler to always have a valid engine identifier. This removes an entire class of bugs, unit tests, and possible future regressions.

Here is the how to create the TurnSmokeUnitOffCommand using EngineID :

/// Error: Value of optional type 'EngineID?' not unwrapped; did you mean to use '!' or '?'?
let command = TurnSmokeUnitOffCommand(engineID: EngineID(187))

Great! The compiler now emits an error.

That is fine, but production code is not going to have hardcoded values. Instead the app grabs the Engine ’s identifier. Let’s change the Engine to use the EngineID .

struct Engine {

    let identifier: EngineID

    // other details omitted
}

Creating the TurnSmokeUnitOffCommand now looks like this:

let engineID = engine.identifier
let command = TurnSmokeUnitOffCommand(engineID: engineID)

Perfect! The TurnSmokeUnitOffCommand no longer has to be concerned with an invalid engine identifier because an EngineID guarantees the underlying identifier is valid. There is no way the command can be created with an invalid engine identifier.

Building An Engine

Here is some example logic showing how we may check the engine identifier in an incoming “engine record” read from aLionel Legacy Base.

func handle(message: Data) {

    // For the sake of being overly explicit, let's assume there is a function
    // to retrieve the engine's identifier from the incoming bucket of bytes.
    let engineIDAsPrimitive: Int = engineIdentifier(from: message)

    // Now take the primitive integer and try to create an `EngineID`.
    // If the initializer fails, then the incoming engine is considered unexpected
    // and we can ignore the rest of the message.
    guard let engineID = EngineID(engineIDAsPrimitive) else {
        // handle invalid incoming engine ID
        return
    }

    // ... continue processing the bucket of bytes
}

The app has just protected itself from unexpected engine IDs simply by relying on Swift’s language features.

Summary

I have been experimenting with these “wrappers” in my Swift projects. I currently find the code much easier to understand and reason about. There may be other Swifty ways to achieve the same result. For now, this approach seems reasonable.





About List