Scala Tutorials Part #29 - Design By Contract
Originally Posted On : 27 Dec 2017
Design By Contract
This is part 29 of the Scala tutorial series. Check here for the full series.
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.
Scala has four methods
ensuring which are present in the
Predef.scala package and they are available by default and no library import is required.
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 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.
This will throw an exception.
assume is very similar.
This also throws an exception.
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.
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.
ensure often work in tandem. Let’s look at an example.
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,
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
If we try to break the ensuring condition,
Turns out that
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.
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
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
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.