Saturday, 6 October 2012

Circle CI and Haskell

This was going to be a long post on how to set up CI and Haskell.  Thankfully I found a simpler way.

One of the most essential parts of Continuous Integration is to have a self-testing automated build.

A CI server (such as Jenkins or TeamCity) aims to simplify this procedure .  Events on the version control system (such as a git push) trigger the CI server which performs a build of the code and runs the automated test suite.   Unfortunately, in my experience, it's always a pain to set these servers up, especially if you aren't using a standard curly brace language.

Circle CI promises one-click automated continuous integration from code pulled from your git repository.  It sounded too good to be true, and initially it was, there way no out of the box support for Haskell.  However, my e-mail to support was quickly answered and the latest version of the Haskell platform was installed and available a few days later (awesome support).

Once the Haskell platform was installed it took me literally 3 minutes to get a working build.


Each server that runs the process is a standard Ubuntu 12.04 and has the Haskell platform installed.  This makes it dead simple to install any extra libraries and then run the necessary cabal commands to configure and run the tests (I used the exitcode-stdio package to run the tests).

Being able to setup CI in a few minutes is really useful and takes a large part of the drudgery away.  Awesome sauce.

I hadn't realized that GitHub had an API for hooks.  As someone who's really interested in dev-tooling, there's a million and one useful ideas that come out from them.  I'd love to see more tools like this (e.g. static analysis, security vulnerability, fuzz testing, bench-marking, profiling, memory checking, code metrics and so on).

Tuesday, 14 August 2012

NATO on Software Engineering

The Nato Science Committee report on Software Engineering (available here) is a fantastic read.

A study group was formed and given the task of assessing the entire field of computer science. The name "Software Engineering" was deliberately chosen as being provocative, in implying the need for software manufacture to be based on the types of theoretical foundations and practical disciplines, that are traditional in the established branches of engineering.

It was hoped in 1968 that the contents of the paper would be widely distributed so that the necessities, shortcomings and trends that are found could serve as a signpost of manufacturers of computers as well as their users.

Back in 1968, there were only about 10,000 installed computers, increasing between 25-50% a year.  The rate of growth was viewed with alarm then with quotes such as:


Particularly alarming is the seemingly unavoidable fallibility of large software, since a malfunction in an advanced hardware-software system can be a matter of life and death.
We undoubtedly produce software by backward techniques 
Programming management will continue to deserve its current poor reputation for cost and schedule effectiveness until such time as a more complete understanding of the program design process is achieved. 
 And my particular favourite quote about software development

Today we tend to go on for years, with tremendous investments to find that the system, which was not well understood to start with, does not work as anticipated. We build systems like the Wright brothers built air planes — build the whole thing, push it off the cliff, let it crash, and start over again.

This embryonic stage of software engineering was an exciting time.  Engineering practises hadn't matured yet (not sure they have now!), but there was spirited discussion about the nature of software engineering.  The model of software engineering described is easily recognizable as the waterfall model.

Notice already the recognition that maintenance is about the same size as implementation and that implementation is an "error-prone translation process".

From the discussion about software engineering came many things that we see as important today.

The need for constant feedback about the system was emphasised many times.  Fraser describes the software development process in a way that sounds very familiar to agile software development.
Design and implementation proceeded in a number of stages. Each stage was typified by a period of intellectual activity followed by a period of program reconstruction. Each stage produced a usable product and the period between the end of one stage and the start of the next provided the operational experience upon which the next design was based. In general the products of successive stages approached the final design requirement; each stage included more facilities than the last. On three occasions major design changes were made but for the most part the changes were localised and could be described as ‘tuning’
The emphasis of working features after each iteration is something we (as a profession) still struggle to do today.  Breaking big features down into bite-sized stories that can be completed successful is a hard problem.

Nowadays we seem to make less distinction between design and implementation.  Techniques such as TDD (coined in 2002? also known as test-driven design) try to ensure good design (or at least good enough) by driving the design through the tests.  Dijkstra probably wouldn't TDD, but he would argue that tests (or more likely formal proofs!)  are a vital part of the design process:



... I am convinced that the quality of the product can never be established afterwards. Whether the correctness of a piece of software can be guaranteed or not depends greatly on the structure of the thing made. This means that the ability to convince users, or yourself, that the product is good, is closely intertwined with the design process itself.
Perlis summed the process of software development rather well:
1. A software system can best be designed if the testing is interlaced with the designing instead of being used after the design.
2. A simulation which matches the requirements contains the control which organizes the design of the system.
3. Through successive repetitions of this process of interlaced testing and design the model ultimately becomes the software system itself. I think that it is the key of the approach that has been suggested, that there is no such question as testing things after the fact with simulation models, but that in effect the testing and the replacement of simulations with modules that are deeper and more detailed goes on with the simulation model controlling, as it were, the place and order in which these things are done. 
So how was software design knowledge shared back then?    There was some talk about sharing of decisions (even the wrong ones to avoid having them repeated).   Naur was already aware of the need for software patterns to be established.
… software designers are in a similar position to architects and civil engineers, particularly those concerned with the design of large heterogeneous constructions, such as towns and industrial plants. It therefore seems natural that we should turn to these subjects for ideas about how to attack the design problem. As one single example of such a source of ideas I would like to mention: Christopher Alexander: Notes on the Synthesis of Form (Harvard Univ. Press, 1964)


It's interesting to read the discussion on high-level languages.  There was an overwhelming agreement that high-level languages are a good thing, but at the time I think the implementation and tools available restricted their availability.  Thankfully, this problem is fixed now (it's difficult to find people arguing against managed languages),

I found this paper an awesome read and recommend everyone reads through it.  The cynic in me enjoyed seeing that the wonderful best practises advocated today have been around for years, it's just they didn't have catchy buzzwords back then.  What I find exciting is that software engineering still hasn't found that silver bullet that improves software development by an order of magnitude.  Software engineering is going to be a challenge for the next 44 years.

I'd really like to see a version 2 of this paper; how has software engineering changed in the last 44 years?  Are there any collated experience reports?

Monday, 9 July 2012

On Types and Tests

There's been a couple of posts recently (Confession of a Haskell Hacker and Unit Testing isn't enough) about types and tests. I thought I'd write down some of my half-baked thoughts about how I think about types and tests.

When I'm writing code, I tend to try to write them in a functional-style. Given some arguments, produce some output. Give the same arguments, get the same answer. This isn't possible everywhere in most applications, but I've found it to be applicable most of the time. Given I've got this function, I now need to test it. I like to think of each function as having a shape.

Let's consider a simple function that scores a Poker hand. Our first signature for this is:

// And so on.
  const int HEARTS = 1;
  const int KING = 10;

  int ScoreHand(PairOfInts[] cards) {}

If I was to visualize this function, I'd imagine something like this:


The values of possible input to this function are HUGE.  It's defined over every single possible pair of integers.  The only interesting area is the tiny red dot in the middle (not to scale) that represents the tiny amount of the possible input that it's possible to return a valid value.  As is already clear this isn't a good way to write a function - you can pass almost anything in, and the chances of getting a sensible response is slim.  The testing burden on this function is huge, whereas the type "burden" on the function is very low.

Let's try and improve things a bit.

   enum Suit { Hearts, Diamonds, Clubs, Spades };
   int ScoreHand(PairOfSuitAndAnInt[] cards) { /* reams of code */}   

This is better, the size of the red circle has increased a tiny amount, but there's still a huge amount of code that it can't (sensibly) produce a value for. This is known as a partial function. We have to test slightly less (no need to test that a Suit is valid), but we still have to consider a huge range of other options (what if there are six cards?).

What we're after is aligning the function so that for every value we can calculate a suitable score.

   enum Suit { Hearts, Diamonds, Clubs, Spades };
   enum Value { One, Two, Three, Four, Five, Six
                 Seven, Eight, Nine, Ten,
                 Jack, Queen, King, Ace };

   class Card {
     public final Suit suit;
     public final Value value;
     // And an appropriate constructor
   }

   int ScoreHand(Card a, Card b, Card c, Card d, Card e) { /* reams of code */}


Now I'm closer to total function. It guarantees (via the type system) to produce a sensible value for each and every input that's passed to it. (I'm just going to willfully ignore that Java/C# allows null values. Let's pretend it doesn't!). The big hole we have is that we don't check the uniqueness of the cards (e.g. I could ask for the score of five aces!).

If I visualize the function now, it's much (that's an exaggeration) more interesting.  Red defines the area that the function produces a sensible value for and the area around the outside is the inputs we don't provide a sensible output for!



Now I can go about testing this in a much nicer way. Traditional unit testing is existential quantification. Given this set of values, I expect this kind of output. It gives some assurance for some values. I'll need some of these tests just as a sanity check.

Property-based testing extends this. In property-based testing I can say things like given any input of the form needed by the function, the following holds. For example, I could write a test that said given 5 unique cards, if I compare them (with the output of the ScoreHand function) I'll lose against a Royal Flush (unless I also have a royal flush).   If you have a total function, Property-based testing allows universal quantification - you can specify an invariant that holds for all values of input.

You should use your type system (if you have one) to constrain the inputs to your functions as much as possible. The more constrained the types, the less you have to test (you still need both!). If you can define a total function, and a set of invariants that hold for all values then you have a incredibly powerful test-suite.