How to program without fear?

Ray Shih
6 min readAug 16, 2018

--

We’ve been there. As a programmer feels fear every time changing the code. We feel more of the emotion if the code was written by ourselves, because there is no other person to blame on. And if the mistake was made by ourselves, we need to take the responsibility. At least, we may feel guilty deep in your heart. This is especially true for the program that automates a lots of tasks to save money. The possibility of creating bugs is the degree of the fear we have. Which is actually a good thing, in a way that it forces us to write better problem. But from time to time, this kind of fear will be amplified. It duplicates itself. And eventually, the fear will occupy the bandwidth of our brain and then slow down the entire development process.

There are different strategies developed to remove or suspend this fear. The most common one is tests. By writing tests, we turn the fear of incorrectness to the test suites. There is even a practice called Test Driven Development (TDD). The practice asks us to write the test before write the program we actually want. That is, we kind of write a program twice but in different ways and kind of compare them before the actual deployment. They are robots to help checking the things we asked them to check every time we change the code. The “robots” are just another program we write. The fear still exists. We just break it down to smaller pieces until it’s small enough to be ignored. But still, this is a way to reduce the fear.

I did full TDD for a small period of time. In some cases, it was literally write almost the same program twice. I recognized this and felt the fear presenting itself in front of me. I still do small scope of TDD these days, but only when I’m sure the logic can be written differently. The fear still there. Since the fear didn’t disappear, what I did is just turn my head to not see them. This small story tells us tests are good only when we remember and are willing to duplicate the work. So there is another human factor in this tactic, and human factors mean uncertainty and also is another layer of fear.

There is another way to reduce the fear: type system. As an engineer majored in Computer Science, I wasn’t aware of the importance of the type system until recently. The first two programming courses we had is C and Java. Although they are both statically typed languages, they were not sold by emphasizing the type system. The type system in C mostly acts as a hint we can provide to help the compiler compiles. For example, division of int and float are treated differently. And we use the char as shorter int to save memory, and even worse, there is an unsigned_char, which implies the types are merely necessary annotations for compilers to arrange the memory.

The official name of the course teaching Java was OOP. So the course was more about the definition of OOP, how to do inheritance and some design patterns. The main idea was how to organize the program by using the “well-known” patterns. As students just started learning programming, we were taught to write slightly bigger, organized program before we understand the needs. Since we don’t really identify the necessity, “stories” of class inheritance are in fact noises we can’t handle while programing. So lots of programmers embrace the world of untyped or dynamically typed languages with either the practice of writing more tests or the attitude of not caring the safety at all.

To understand why type system can reduce the fear more than tests, we can’t rely on the knowledge learnt from the OOP courses since they didn’t talk about how to use type system at all. So let’s go through the evolution of how I gradually figured out the help type system can provide.

Basic Episode: Enum

Enum exists almost in any language, let’s take C as our first example:

It will print out Monday. The enum syntax just provides a way to map tokens into ints, i.e. Monday is 0, Tuesday is 1, etc. This exposes some problems.

Problem 1: The enum doesn’t guarantee anything at all.

The type “weekday” is just int, so printDay(Monday) is the same as printDay(0) . Then guess what? We can write printDay(5) and the program will be compiled successfully, but it won’t print anything.

How about Java? Does Java provide more protection while the developer using enum? The answer is yes, we cannot simply fill numbers into the function. But Java has another problem:

Problem 2: No check for unhandled cases

Please notice that I intend to remove the cases after Tuesday . This code compiles. But the similar problem happens: there is nothing printed. Although it prevents developers to put random stuff into the printDayfunction, it is still developers’ responsibility to remember handling all the cases. Most of the languages used daily has similar issues. Does that mean it is beyond the scope of the compiler capability in theory? Let’s take a look how PureScript handle this:

The Weekday data type defines 5 different values, and it‘s not like C, it doesn’t equal to other values. So we cannot put values other than type Weekday into the function. Secondly, the compiler will force us to implement all the conditions this function might have. You can try to remove some cases in this Try PureScript and see how the compiler can remind us.

Basic Episode: Maybe/Optional — make invalid state unpresentable

Null pointer exceptions(NPE) are now well-known, and lots of new languages have designs specifically for handling null exception. Let’s review the common Exception in Java:

int[] arr = new int[]{};
System.out.println(arr[0]);

The program still compiles. But the array is empty, so the ArrayIndexOutOfBoundsException is thrown at the runtime. So how we normally handle this in Java? Option 1: use the assertion to “fail-fast”. It’s sometimes not feasible since we need to recover from failure. Option 2: manually check the array size before accessing the content:

int[] arr = new int[]{};
if (arr.length == 0) {
System.out.println("Empty");
} else {
System.out.println(arr[0]);
}

This is a simple example, so there is pretty obvious that we need to handle the empty case. Novices might think this is just because the programmer is not careful enough. But it’s actually very common and inevitably slow down professional developers. Please recall that almost every time we had a NPE, we just couldn’t know the root cause at first glance. Let alone that it’s almost impossible to detect it right after the code being changed. So what do we do? Every time there is a NPE, we fix the root cause and add the corresponding test to prevent it happens again. There is almost no way to prevent NPE beforehand by simply using tests. Does that mean we reach the limit of type checker? Nope, let’s see how other languages handle this.

Just like the enum problem of the last episode, those languages’ compilers will remind developers to handle the null case, for example, in Swift:

let arr: [String] = []
print(arr.first.count)

This code won’t compile at all. The compiler will complain the first element is a “wrapped” String, so we need to “unwrap” it before accessing the count. The “wrapper” Swift uses is the construction Optional. With this and its type, Swift guards developer from accessing potential nil value. Such that, we need to handle this layer of enforcement:

let arr: [String] = []
if let s = arr.first {
print(s.count)
} else {
print("Empty")
}

The if let statement helps to check whether the value is nil and if it’s not then assign the value to s. And now developers can take a rest from worrying the NPE. There is a very similar example in Android realm: Kotlin. Kotlin also uses this approach:

val emptyArr = arrayOf<String>()
val v = emptyArr.firstOrNull()
println(v?.length ?: “empty”)

Unfortunately, it is not entirely enforced. A careless programmer may just use force unwrap operator like this:

val emptyArr = arrayOf<String>()
val v = emptyArr.firstOrNull()
println(v!!.length) // NPE thrown here

Some developers would say: you don’t need to learn too many programming languages because they are basically the same. But as above examples, many new languages with new syntax try to help developers to innovate faster by providing better safety features. I recommend to understand those subtle differences and learn how to leverage those features to write better program. Also, knowing the features of the language you use to NOT shoot yourself in the foot.

There are more episodes I would like to talk about in the future. Here is the list of topics planned:

  • Episode: Type A is not Type B — let the type system guides you
  • Episode: Generic is higher order type
  • Episode: You can not conflict with yourself — Phantom Type
  • Advanced Episode: Encode side effect in type system
  • Advanced Episode: Encode logic in type system — stronger types
  • Advanced Episode: Type level programming — code generation
  • Extra Episode: Maybe is not enough — decidability
  • Extra Episode: Your Totality is Not Total — halting problem is real

Thanks for reading! Any comments are appreciated :D

--

--

Ray Shih

Functional Programming Advocator, Haskell, Idris, PureScript Lover. Work at Facebook and Machine Learning student.