Scala Tutorials Part #29 - Design By Contract


Design By Contract

This is part 29 of the Scala tutorial series. Check here for the full series.

Index

Introduction

Design by contract is a programming approach where there are preconditions and post-conditions to a code block and if these conditions are not satisfied an exception is thrown to the caller. This was originally designed by Betrand Mayer who was the creator of the Eiffel programming language.


Picture courtesy - Wikipedia

Some languages do not have first class support, some have support through external libraries. Scala has some support through the language itself and that is what this post is all about.

High Level Overview

Scala has four methods assert, assume, require and ensuring which are present in the Predef.scala package and they are available by default and no library import is required.

Of these assert, assume and require are preconditions and ensuring is a post-condition. Preconditions are meant to be guards before entering a code block while a post condition happens happen after execution i.e like a do while construct. Code examples below will make them clear.

Assert and Assume

The assert keyword would be familiar if you have done unit testing before. It takes in a boolean condition as a parameter and checks if it is true else throws an exception.

val age = 20

assert(age>20)

This will throw an exception.

Exception in thread "main" java.lang.AssertionError: assertion failed
at scala.Predef$.assert(Predef.scala:156)
.....

assume is very similar.

 grantLicense(16)

def grantLicense(age:Int): Unit = {
assume(age>18)

//Business logic

This also throws an exception.

Exception in thread "main" java.lang.AssertionError: assumption failed
at scala.Predef$.assume(Predef.scala:185)
.....

In fact, it throws the same exception java.lang.AssertionError only with the name changed such as assertion failed or assumption failed depending on what we use. Even the source code is almost identical.

//Taken from Predef.scala

//Assert
def assert(assertion: Boolean) {
if (!assertion)
throw new java.lang.AssertionError("assertion failed")
}

//Assume
def assume(assumption: Boolean) {
if (!assumption)
throw new java.lang.AssertionError("assumption failed")
}

So why the almost identical ones? The answer lies in how they should be used.

assert is kind of like attempting to prove something. It can be regarded as a predicate in a mathematical sense i.e which needs to be checked by the static checker. assume on the other hand is like an axiom where the code can rely upon the static checker to throw an error if its not true. So they are in some sense just syntactic sugar.

Require and Ensuring

Methods require and ensure often work in tandem. Let’s look at an example.

def squareEvenNumbersWithLimit(num:Int,limit:Int) : Int = {

require(num%2==0)

num * num

} ensuring(num * num < limit)

This can fail in two ways i.e the passed number is not an even number and also when the squared result is greater than the limit that is passed in.

Calling it with the below parameters,

squareEvenNumbersWithLimit(3,200)

Causes a java.lang.IllegalArgumentException. This blames the caller for giving a value that is termed illegal as per the contract. Notice how this is different from the java.lang.AssertionError. One is an exception and another is an error. I have already explained this in a previous blog - exception handling in Scala

Exception in thread "main" java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:212)
....

If we try to break the ensuring condition,

Exception in thread "main" java.lang.AssertionError: assertion failed
at scala.Predef$.assert(Predef.scala:156)
....

Turns out that ensuring calls assert internally. One more thing to note is that ensuring cannot access variables inside of the method block. Its scope is limited to the method parameters.

Ensuring error

Conclusion

We have briefly seen what these methods can do. These can be combined to form complex design patterns such as Design by Contract, Defensive Programming etc., An important thing to note is that assert and assume can be removed using a compiler flag -Xdisable-assertions during compile time. This is useful when you don’t want such statements to enter into production.

There are advanced patterns possible. Notice that all of these functions in Predef.scala have another signature where functions can be passed in i.e they are higher order functions.

In the Scala world these patterns are not extensively used. This is because they throw an exception during run time. There are better design patterns where we can encode such conditions during compile time. We have already seen the Option type which is one way of encoding state within a container which is compile time. There are other ways such as Either, Try etc., We will take a look at those in later tutorials. With that said, there are situations where you might want to use design by contract, such as dealing with situations where null is quite possible(interfacing with java code) and then throwing an exception.


Tagged Under


Scala


Search this website...




Keeping up with blogs...

I blog occasionally and mostly write about Software engineering and the likes. If you are interested in keeping up with new blog posts, you should follow me on twitter where I usually tweet when I publish them. You can also use the RSS feed , or even subscribe via email below.

Feedio Subscribe


Share & Like

If you like this post, then you can either share/discuss/vote up on the below sites.



Thoughts ...

Please feel free to share your comments below for discussion

Blog comments powered by Disqus