Archive for September, 2008

Another look at the anti-IF campaign

Friday, September 19th, 2008

Last year, the Italian XP pioneer Francesco Cirillo launched his famous “anti-IF” campaign. The idea is bring down the complexity of programs by using object-oriented design; the most important trick in that bag is to replace case analysis by polymorphism. For instance, to replace

  if (shape.type == SQUARE)
    area = shape.getSide() * shape.getSide();
  else if (shape.type == TRIANGLE)
    area = (shape.getBase() * shape.getHeight())/2.0;
  else if (shape.type == CIRCLE)
    ...

by the much nicer

  area = shape.getArea();

There are many other useful tricks from OO design. But I’d like to talk about another set of tricks for eliminating IFs, namely the bag of tricks of algorithm design.

Boolean values are values too.

One simple trick is to replace

  if (foo and bar or baz)
    return true;
  else
    return false;

by

  return foo and bar or baz;

Now, some people prefer the first form, because they prefer operational reasoning over reasoning over boolean values (see the comments to a previous post on this subject). I much prefer the firstsecond, for one thing because it’s shorter. Concision is power. And I try to avoid operational reasoning as much as I can. If we push operational reasoning to the extreme we get:

  if (foo) {
    if (bar) {
      return true;
    }
  }
  if (baz) {
    return true;
  } else {
    return false;
  }

This should be equivalent to the previous two examples. But it’s not immediately obvious that it is. It requires much more thinking, and thinking is something I must save for important things.

Encapsulate IFs in well-known functions

Suppose you must find the maximum value of two numbers. Please don’t write

  if (x > y)
    return x;
  return y;

It’s much better to write

  return max(x, y);

Try to extend these two examples to the maximum of three numbers and see which one turns out clearer. The fact is that while we stay in the realm of expressions, we can exploit many nice properties, such that max is associative; in the realm of IFs it’s much more difficult to reason. You must simulate execution in your head, and that takes energy away from you; energy you might spend on other, more important problems.

Another example: replace

  if (x < 0) return -x;
  return x;

by

  return abs(x);

If your language does not provide an abs function, you can define it nicely as max(x, -x).

Zero is a perfectly good number

This code returns the sum of the elements of an array.

// ugly and wrong
int arraySum(int[] array) {
  int sum = array[0];
  for (int i=1; i < array.length; i++) {
    sum += array[i];
  }
  return sum;
}

Can you spot the error?

*
*     *

Yes, it breaks for empty arrays. If we keep it this way we'll be forced to pull ifs in the calling code, in all places where we call this method:

  // very ugly 
  if (x.length > 0) {
    s = arraySum(x);
  } else {
    ???
  }

This is ugly, and verbose. And it's not entirely clear what should happen when the length is 0. Should we give up and throw an exception? It's much better to stop treating 0 elements as an error. An empty array is a perfectly good array, and the sum of 0 elements is 0.

  // still ugly 
  int arraySum(int[] array) {
    if (array.length == 0) {
      return 0;      
    }
    int sum = array[0];
    for (int i=1; i < array.length; i++) {
      sum += array[i];
    }
    return sum;
  }  

Now it's correct, but there's no need to treat an empty array as a special case:

  // good
  int arraySum(int[] array) {
    int sum = 0;
    for (int i=0; i < array.length; i++) {
      sum += array[i];
    }
    return sum;
  }  

Now if the array is empty, the loop will be executed 0 times, since 0 < 0 is false. The result will be correct for all N ≥ 0.

Whenever you have to apply a binary operator such as "+" to a set of values, you should ask yourself what should be the answer for 0 elements. If the binary operator has a unit element, that is an element that leaves the other operand unchanged:

  0 + n = n + 0 = n, for all n
  1 * n = n * 1 = n, for all n
  max(−∞, n) = max(n, −∞) = n, for all n

the result of applying the operator to a set of 0 operands should be the unit element. The sum of 0 numbers is 0, the product of 0 numbers is 1, the maximum of 0 numbers is −∞. Why is that? Because it's the only logical answer. Think about this: if you split array A with 10 elements in two parts, A0 and A1, with 5 elements each, you expect that sumArray(A) be equal to sumArray(A0) + sumArray(A1). You expect exactly the same result if you split A so that A0 has 3 elements and A1 has 7. It's only natural to expect the same result if you split A such that A0 has 0 elements.

Calcolare gli assegnamenti

Thursday, September 18th, 2008

Ieri sera alla riunione del Milano XP User Group abbiamo parlato di come “calcolare” gli assegnamenti, ovvero, come fare in modo che uno statement conservi un invariante e allo stesso tempo faccia progresso verso la fine del loop. Ad esempio, supponiamo di voler calcolare una tabella di quadrati, con la limitazione che non possiamo usare la moltiplicazione. (Perché no? Magari perché sul nostro processore la moltiplicazione è lenta). Abbiamo due variabili s e n che soddisfano l’invariante s = n2. Vogliamo incrementare n, e conservare l’invariante. In altre parole, vogliamo trovare un’espressione E che soddisfi

{ s = n2 } s, n := E, n+1 { s = n2 }

In pratica questa è un’equazione in cui l’incognita E è l’espressione che manca per completare il programma

  
  while n != N do
     s, n := E, n+1
     println "il quadrato di " n " è " s
  end

L’obiettivo è essere in grado di calcolare l’espressione E che mi serve, senza doverci “pensare”. Qual’è allora la procedura? Per dimostrare

{ P } x := E { Q }

devo dimostrare che P implica Q[x:=E], dove Q[x:=E] significa sostituire x con E all’interno di Q. Nel nostro caso:

   s = n2[s, n := E, n+1]
sse // applico la sostituzione
   E = (n+1)2
sse // svolgo il quadrato
   E = n2 + 2n + 1
sse // sfrutto l’invariante
   E = s + 2n + 1
sse // le moltiplicazioni sono vietate
   E = s + n + n + 1

Voilà: ho dimostrato che s=n2 implica s=n2[s, n := E, n+1] se scelgo E=s+n+n+1. Il programma desiderato è dunque

  
  while n != N do
     s, n := s+n+n+1, n+1
     println "il quadrato di " n " è " s
  end

Per saperne di più:

Next stop: Eindhoven

Tuesday, September 9th, 2008

My new workshop proposal has been accepted at the XP Days Benelux conference. It’s a new experiment for me; I never attempted to teach things related to my Ph.D. work before.

What is the connection between program derivation and agile methods? Here follows my answer; it’s part of the handout I will prepare for the workshop.

One of the principles of the Agile Manifesto is “technical excellence enhances agility.” This is a crucial principle; you can’t expect to have success unless your technical skills are as high as possible. Trying to be agile without paying attention to technical issues is dangerous.

Think about it: when you work in an agile team, you give up doing extensive requirements before coding. This increases the likelihood of getting totally unexpected requirements halfway through; requirements that your architecture does not support (and here I define architecture as “that which is difficult to change later”). Then you give up an extensive design phase. This increases the risk that your codebase will turn into a ball of unmanageable spaghetti.

Agile methods use specific practices to compensate for the absence of the requirements and design phases: test-driven development, refactoring, pair programming and simple design. But these practices _are not easy to do_. Before you even attempt to be agile, you should get your fundamentals right. This means you should know your operating systems, algorithms and data structures, design patterns, object-oriented design and even UML by heart. For how are you going to do “simple design” when you don’t know what your design options are? How are you going to refactor, if you don’t know where to refactor to?

The theory I’m talking about is the work of, mainly, Sir Tony Hoare and Robert Floyd, who invented the axiomatic theory of programming, and Edsger W. Dijkstra, who pioneered the idea of developing a program and its proof of correctness concurrently, in small incremental steps.

A modern introduction to the subject is Program Construction by Roland Backhouse.