On unit testing and type checking

As most software developers know, it’s hard to create robust software. There is a lot of software out there that’s got bugs and a lot of time the developer doesn’t know about it and the users of the software run in to them. This is of course a very frustrating thing for both developers and users.

Even for skilled developers, if a project grows big enough it becomes a lot harder to understand and maintain. Changes in one part may influence a seemingly unrelated part in the software. For example, in a language like Javascript, if you add a parameter to a method, it’s easy to forget to update one or two other functions that call it, but you’ll find out at runtime. Hopefully. Or else your customer will find it out for you.

It would be great if software developers could say with confidence that they have working software. Most of the bright Ruby developers I know strongly advocate test-driven development, not only to verify their programs but also to help them design the program. Programmers in a strongly typed language like Haskell often do the same thing: they’ll first write down the type, which forces them to really think about the function. The type of a function states what the input is, and in a pure language, you even have to state the kind of side-effects that can occur.

Both unit testing and strongly typed programming forces you to build your software in such a way that it is easily testable. They encourage you to write small functions that perform one specific task. Your program will almost automatically be broken down into small bits. I hardly find myself writing functions that are longer than a couple of lines, which also helps me to understand the functions even if I haven’t looked at them in a while.

In both unit testing and purely function programming you have to be explicit about state. What needs to be set up before you can call a function? Will there be side-effects? What should the state be like afterward?

Over time, programs will also evolve, and at some point, you’ll have to do refactoring. If you have correctly specified your unit tests, you can easily change a method and the tests will show you where it breaks. In Haskell, I very often change a method’s type signature and implementation, knowing that the compiler will tell me what I broke.

To summarize: unit testing and strongly typed (pure) programming overlap a lot. With unit testing you can specify a lot of things that you can’t always easily do in your type system. Most notably, you can inspect the results of functions, which is something that you can not do in, for example, Haskell. There are other type systems that do allow you to do this, but that’s beyond the scope of this article. On the other hand, when working in Haskell, you’ll get the types for free most of the time. Instead of starting with a type, you can also start with the implementation and let the type-checker infer the type for you.

Both test-driven development and type-driven development can help you to make your code more understandable and better designed. I believe that, although there are a lot of differences, both approaches will help you build programs that are more safe and easier to understand. I guess all the unit-testing rubyists and purely functional Haskellers are not so different after all.


About this entry