Sunday, July 26, 2015

On Being Joyful By Contract

"How joyful am I made by this contract!"
-- Shakespeare, Henry VI Part I
Act III, Scene I, Line 144

I can think of no book that has had more impact on my career than The Pragmatic Programmer by Dave Thomas and Andy Hunt.  One of the tips in The Pragmatic Programmer that I sadly do not find myself using a lot is Design with Contracts.

Design with Contracts
Use contracts to document and verify that code does no more and no less than it claims to do.

-- Dave Thomas and Andrew Hunt, The Pragmatic Programmer

Clojure has pre: and post: assertion functions, let us look at an example of the pre: assertion function in a prime tester function.

(ns primes)
(defn prime? [n]
{:pre [(number? n)
((comp not neg?) n)]}
(cond
(<= n 1) false
(some (comp zero? (partial mod n)) (range 2 n)) false
:else true))
view raw Primes.clj hosted with ❤ by GitHub
(ns primes.tests
(require
[primes :as sut :refer [prime?]]
[clojure.test :refer [deftest testing is run-tests]]))
(deftest primes-tests
(testing "Given a non-prime number prime? must return false"
(is (false? (sut/prime? 0)))
(is (false? (sut/prime? 1)))
(is (false? (sut/prime? 4)))
(is (false? (sut/prime? (* 2 3 5 7 11)))))
(testing "Given a prime number prime? must return true"
(is (true? (sut/prime? 2)))
(is (true? (sut/prime? 3)))
(is (true? (sut/prime? 5))))
(testing "Given a number less than 0 prime? must throw an AssertionError"
(is (thrown? java.lang.AssertionError (sut/prime? -1)))
(is (thrown? java.lang.AssertionError (sut/prime? -111))))
(testing "Given a non-numeric value prime? must throw an AssertionError"
(is (thrown? java.lang.AssertionError (sut/prime? "error")))
(is (thrown? java.lang.AssertionError (sut/prime? {:error true})))))
(run-tests 'primes.tests)

We can easily see this is not the worlds greatest prime test function, but the point is to look at the pre: assertion function.

We see that in order to call this function we must have the following: 1) the argument used in the call must be a number (defined by number? as instance? Number) and 2) the argument used in the call must not be negative.  We see by the tests that if either pre: condition is broken an AssertionError is thrown.  Thus allowing us to assume in the body of the function that we have a number and that it is not negative.

If one designs with contracts then one can easily reason about a system since one knows what each part of the system can produce.