Guardians of the code

Hernan Rajchert
6 min readSep 7, 2018

--

As developers, we don’t want to spend our sprint time hunting bugs, and we don’t want to be called at 8 pm on a Saturday night because our software is behaving strangely. We want to have confidence in what we do, we need a hero that guards us and protect us from making mistakes that could have been easily avoided. We need to start programming more defensively! We need… Contracts???

Unexpected behaviour can cause some serious bugs

Contracts to the rescue!

Every time we program we define contracts, even if we don’t do it consciously when we define a function we define a contract stating the parameters it can receive and the relations and restrictions they may have. When we call a function, we are agreeing to its Terms and Conditions, and if its 3rd party code the contract will normally be defined in the form of documentation.

JavaScript Array.indexOf method is an excellent example. As described in the documentation we can use it to search if an element exists in the Array. It has two possible arguments, the first one is mandatory, and the other one is optional.

JavaScript array’s indexOf method

It’s worth noting all the conditions that relate to the second argument. What happens if we don’t pass it? What happens if it’s a negative number? What happens if it’s a number greater than the size of the Array? What happens if we make a mistake and pass a string?

If we were to program such a function ourself, we would probably do something like this

Where we define pre-conditions that our parameters must comply before doing the actual work. These pre-conditions are also known as Guards, as taken from the Wikipedia definition:

In computer programming, a guard is a boolean expression that must evaluate to true if the program execution is to continue in the branch in question. Regardless of which programming language is used, guard code or a guard clause is a check of integrity preconditions used to avoid errors during execution.

To strengthen our contracts we could also define post-conditions, that as its name implies are executed after the actual code runs. In the case of indexOf we could assert that the response is a number lower than the size of the array or the number -1.

Both pre and post conditions give us guarantees about how we program. The first protects the function from outside calls and the second protects the function from itself.

TypeScript For The Win

One of the benefits of using guards is that they follow the fail fast principle. Instead of letting the error propagate or be swallowed, they warn you as soon as they see a mistake and help you pinpoint the origin.

To fail even faster, we can use a static type checker such as TypeScript. It works as the police of our programs, allowing us to enforce the contracts we write at compile time. In the following example we refactored one of the pre-conditions by adding type annotations:

TypeScript enforces your contracts👮

If we call our function and we don’t adhere to its contract, it will fail at compile time, which means faster development cycles as we don’t even need to try the code, the compiler will point us in the right direction.

Our mental complexity has clearly gone up as generics are more complicated than a simple if statement, but we gain more guarantees. A question that we didn’t asked before is: what happens if we have a list of numbers [1, 2, 3] and we search for a string, for example “3”? The answer is probably what you expect, we receive a -1.

If we don’t use a type checker, indexOf’s contract will silently swallow a bug. By using TypeScript we’ll get an error that makes us ask the right question: why were we looking for a string in an array of numbers in the first place?

We can also add a return type to our function to act as a post-condition, enforcing us to return a number. But you should know that not all type systems are created equal and they may give you different guarantees. For example, TypeScript can’t enforce you to return a number in the correct range, while other more restrictive languages such as IDRIS could.

Type guards

TypeScript has a concept called Type Guards that is related to what we talked so far, but instead of continuing or not the program execution, it will narrow the type of a variable in a scope depending on some dynamic checks.

Let’s consider the following code:

If we run it in the JavaScript console and the element exists, everything will be ok, but if it doesn’t we’ll get the runtime error: Cannot set property ‘innerHTML’ of null.

If we use the TypeScript compiler with the strictNullCheck option enabled, the function getElementById will return the union typeHTMLElement | null”. So it doesn’t matter if the element exists or not, we’ll get a compile time error saying that the object may be null.

By adding a simple clause we fix the error.

This is possible thanks to Type Guards. TypeScript uses code flow analysis to try to understand all the possible types a variable can have in different parts of the code. In the first line, header can be “HTMLElement | null”, the only way to get to line 4 is if header is thrutly, so TypeScript will narrow its type to HTMLElement. Similarly, the only way to get to line 6 is if header is falsy, so the type will be null.

Type Guards can also be used to define algebraic data types (ADT) to separate logic from data. Let’s examine the following code:

Inside the function doYourThing we don’t know too much about animal. We know it has a property called species as both Cat and Dog have a property with that name. The type of the property will be the union of the possible types, in this case ‘cat’ | ‘dog’.

The only way to reach line 19 is if species equals to ‘dog’, so the Type Guard will narrow animal’s type to Dog in that block, allowing us to use the property favoriteHuman. Similarly, if we reach line 21 is because animal is of type Cat thus will have the property nemesis.

We shouldn’t be able to reach line 23 because species can only be ‘cat’ or ‘dog’ and both conditions were checked inside the function, so the Type Guard concludes that in that block animal type is never. This allows us to do something called exhaustiveness checking, a pattern that enforces us to check all possible values in the ADT. If we add a new Animal in line 11 and we don’t modify our function, we’ll get an error because we cant pass it to assertNever.

You can see a live demo in this talk by Anders Hejlsberg, a core developer of the language.

Custom Type Guards

TypeScript comes with a variaty of Type Guards already predefined. We have instanceOf, typeOf, and literal values out of the box, but there are scenarios where we need to define our own Guards.

The concept that we used in the previous section to create our ADT’s is called discriminated unions, where we take advantage that all the types in the union share a common property. But what happens if the types are not that homogeneous.

This code will throw an error at compile time on line 15, as TypeScript can’t assume that feline has a property called species. If we try to hint the compiler by casting to Cat, we’ll break the Type Guard and cause an error on the assertNever.

To make this example work, we need to create a custom Type Guard, which is nothing more than a function that returns a boolean. The syntax is quite easy, you receive a parameter x with some general type and return a type x is foo to indicate that if the condition is true then x is of type foo.

And now our example works just fine 👍. If you want to see a real example of custom type guards, you can check how we use the variadic isInstanceOf and caseError to correctly manage our errors using Task.

That’s it for now! Let me know what you think of the article on twitter @sherman3ero, all feedback is welcomed ❤️.

I generally write about TypeScript and functional programming. Please tell me what topics would you like to read about.

Cheers 👋

--

--

Hernan Rajchert

I’m a Software Engineer focused on Web Technologies (full stack). I ❤️ typed languages and FP.