Archive for the 'essay' Category
Friday, August 6th, 2010
I won’t bore you with the story of how long it took for people to recognize that zero is a number. Without zero it would be difficult to explain what is the value of, say, 3 minus 3; we’d be forced to say that it’s a “meaningless” expression. Funny huh? Yet some developers seem to be stuck to medieval thinking in this respect.
Have you ever seen code like this?
public List findAllEmployeesByDepartment(int departmentId) {
String sql = "select * from employees where department_id = ?";
ResultSet rs = select(sql, department_id);
if (rs.size() == 0) {
return null;
} else {
// ... convert the recordset to a List and return it
}
}
This developer seems to think that an empty List is not a regular list, so he thinks he should return a special value like null to signal that the query returned no values. This is totally unnecessary. No, I take it back: this is totally wrong. You are forcing all callers of findAllEmployeesByDepartment to check for null. Not only that; this code seem to say that it’s a totally unnatural and unexpected thing for this query to return no rows. Soon developers will forget to check for null, and the application will throw NullPointerExceptions.
A related example is:
Foo[] foos = ...;
if (foos.length > 0) {
for (int i=0; i < foos.length; i++) {
// do something with foo[i]
}
}
Here the developer thinks that they have to treat the case of an empty array separately. In fact the IF is totally unnecessary. If the array is empty, the loop would execute zero times anyway. Java (and C) arrays use asymmetric bounds, which make it easier to write code that does not need to treat a zero-size interval as a special case.
In conclusion: empty collections are perfectly valid collections, and empty arrays are perfectly valid arrays. It’s a good idea to write code that doesn’t treat “zero” as a special case.
This post is part of a series on development fundamentals.
Posted in Fundamentals, essay | 2 Comments »
Tuesday, June 29th, 2010
Know your stuff
A while ago we had a fun evening at the Milano XPUG writing a Sudoku solver. I blogged about my solution. I’m not particularly proud of it, in retrospect. The code and the tests are not obvious. I can’t read any of it and be certain that it works. It does not speak.
It is true that solving puzzles like Sudoku is quite different from what application programmers do everyday at work. Why is it that? The problems that we solve in business applications do not have that mathematical crispness that puzzles have. Perhaps it’s because we’re not good enough at analyzing them and expressing them abstractly. That would explain why business code is so long, convoluted and expensive.
Anyway, the point I want to make is that it is not satisfying to use the tests in TDD as a crutch for constructing hapazard code that, with a kick here and a few hammer blows there seem to work. The point of TDD is to *design* code; and a good design shows how and why a solution works.
I often see people doing katas that involve problems with well-known solutions. We usually disregard, forget, or ignore the well-known solution! And we keep writing tests and production code until we rig together something that passes the tests. It’s painful. I know. I too did some of that.
TDD does not work well when we don’t know what we’re doing. Some high-profile XPers failed to ship when TDDing their way with unfamiliar APIs or disregarding known solutions. TDD is no substitute for analyzing a problem, and finding abstractions that make it easy to solve. TDD without thinking and analyzing and abstracting is not fun!.
It’s for this reason that there is the XP practice of “spiking solutions”, that is, take time to learn how to do something, make experiments, then apply what you learned. If you know how to do things, you will not spend hours discussing with your pair; you and your pair will grab the keyboard from each other, as Francesco Cirillo often says.
A better way
Consider Sudoku again. Peter Norvig solves it in two different ways by using known techniques. The first solution is depth-first search, which is gueranteed to terminate as the graph of Sudoku states is acyclic. The other is by constraint propagation. If I were to do the exercise again, I would try to make the analysis apparent from the code.
Say we want to solve it by depth-first search. That entails two sub-problems:
- a) writing a depth-first algorithm
- b) writing something that enumerates legal moves in a given Sudoku board
I would start by testing the depth-first search algorithm. I would drive it with an abstract “tree” implementation. This means I could concentrate on the search algorithm without being distracted by the complex details of the Sudoku rules.
Then I would test-drive the generation of next-moves from a Sudoku position. That could also be done incrementally. Can we imagine a simplified Sudoku board? The full Sudoku is too complex for the first test! A year ago I would have started by defining a 9 by 9 array of numbers, but now the sheer boredom of defining it would stop me. Is there a better way?
Relax. Think. Dream.
Think about the game terminology. As Norvig says, the game is about units (either a row, a column or a box). A unit with no empty spaces has no descendant moves. A unit where a number is missing has the full unit as a descendant move. A unit where two numbers are missing… You get the point.
Then work out how to combine descendant moves of two units that share a square. Think a row and a column. If the common square is empty, than valid solutions for that square must be valid for both units…
The point is to work the problem incrementally. Try smaller scales first. Try 2×2 boards. Make the size of units and the board parametric. Add the constraint rules one by one, so that you can test them separately.
Conclusions
One important principle to apply is “separation of concerns”. Enumerating moves is a problem, and search strategy is another. By solving them separately, our tests become more clear and to the point. We gain confidence that we know how and why our code works.
Another way to see this is to decompose a problem in smaller problems; prove with tests that you can solve the subproblems, then prove with tests that you can solve the composition of the subproblems.
When you have a problem that is of fixed size 42, turn that constant into a parameter and solve the problem for N=1, N=2, … Imagine if the Sudoku board was 100×100 instead of 9×9; would you define a 100×100 matrix in your tests? Turning these constants into parameters make your code more general, your tests more clear, while making the problem *easier* to solve!
To summarize, what I think is important is
- Learn data structures, algorithms, known solutions, the proper way of doing things.
- Apply separation of concerns.
- Solving a slightly more general problem sometimes is much easier than solving the actual problem
- It’s more fun to work when you know what you’re doing!
Update
Charlie Poole recently posted this on the TDD mailing list (Emphasis is mine):
I’ve written elsewhere that I believe attempting to get TDD to “drive” the invention of a new algorithm reflects an incorrect understanding of what TDD is for.
TDD allows us to express intent (i.e. design) in a testable manner and to move from intention to implementation very smoothly – I know of no better way.
OTOH, you have to start out with an intent. In this context, I think that means you need to have some idea of the algorithm you want to implement. TDD will help you implement it and even refine the details of the idea. Writing tests may also inspire you to have further ideas, to deepen the ones you started with or to abandon ideas that are not working out.
Vlad Levin blogs thoughtfully:
one of the first rules I teach my students when I am doing a TDD workshop or teaching a course is precisely that TDD is not an algorithm generator! Solving sudoku is just the kind of problem you want to find an algorithm for first, then implement that algorithm
[...]
So what is the purpose of TDD then? One goal of TDD is to reduce the need to determine ahead of time which classes and methods you’re going to implement for an entire story. There’s a large body of shared experience in the developer community that trying to anticipate such things tends to lead to paralysis where nothing useful gets done and/or produces bloated, over-designed code. Instead, you can develop one aspect of the story at a time, using each test to keep yourself moving forward and refactoring the design as you go along
Posted in Agile, Expressive Code, essay | No Comments »
Friday, September 11th, 2009
The common wisdom is that Ruby is slow and Java is fast. In general, it’s true. But is it always? Look at this simple test.
$ cat hello.rb
puts "Hello world!"
$ ruby hello.rb
Hello world!
$ time ruby hello.rb
Hello world!
real 0m0.008s
user 0m0.004s
sys 0m0.003s
$
So it looks like it takes 8ms to run a simple “hello, world” in Ruby. How does Java compare to this?
$ cat Hello.java
public class Hello {
public static void main(String ... args) {
System.out.println("Hello, world!");
}
}
$
$ javac Hello.java
$ java Hello
Hello, world!
$ time java Hello
Hello, world!
real 0m0.122s
user 0m0.061s
sys 0m0.028s
$
Even if we ignore the time it takes to compile the Java program, it looks like running the “Hello, world” in Java takes 15 times longer than Ruby. This is due to the long startup time of the Java Virtual Machine. The times you see here are taken on my MacBook Pro; they will be different on other operating systems, but not much different.
So what, you will say? “The startup time is not important! As soon as the JVM is up and running, Java can run circles around Ruby.”
I don’t agree that startup times are not important. The startup time for Java becomes much worse when you run complex applications. A vanilla Tomcat with no web applications installed takes about one minute to start up. Compare with Webrick, the Ruby web server, that is up and running with my web application in 3 seconds. The difference in startup times makes all the difference in the world when you’re developing software. It takes at least one minute, often much longer, to start up a Java application so that I can try it. There are times when you’re developing an application when you need to test it after each tiny change. It’s very difficult to do that in Java. The problem is made much worse by the fact that in general Java “containers” can’t reload changed classes without a restart. (Webrick can do that.)
What, you will say? “Matteo gave up TDD! He tests applications manually by clicking around like a monkey!” No, really, it’s not like this. I always write production code with TDD. That does not mean that you *never* test your stuff manually on the live application. Quite the opposite: there is a danger, with new converts to unit testing, that we trust our tests too much. I’ve seen people declare a story “finished” when all the unit tests are green, without ever checking if it really works! And of course, if you never test it manually, it will not work. There is a need for manual testing (some call it exploratory testing), even if you’re Kent Beck or Misko Every.
So I hope you’ll agree with me that short startup times are important for developers. But there are other implications. The fact that it takes a lot of time to startup a Java application means that Java developers are trained to write applications in a single JVM process. For instance, we often see dozens of web applications running in a single Tomcat. If you need concurrent operations, the solution is always to run more threads within the same process. And there is a big problem with this.
The operating system’s concept of a “process” is a very useful one. A “process” is a bundle of threads and resources: memory, open files, network connections, and the like. A process in Unix or Windows is a watertight compartment. When a process terminates, *all* of its resources are released. A process cannot easily corrupt the state of another process. A process can be given limits on how much memory or CPU it can take. It’s very useful to organize a concurrent application as a set of cooperating operating system processes. That’s the way the Apache Http server works, and that is a remarkably reliable software. It’s also one of the smart ideas in the Chrome browser, to run each tab in a separate process.
It’s a good design principle to have many small modules communicating with well-defined interfaces, rather than a single monolith where all the threads can interact in unforeseen ways. It’s also the way of Unix to design applications as collections of small communicating processes. Which makes me wonder how could Sun ever get us to believe that it’s a good idea to put all of our eggs in a single, huge process. Should not Sun be the champion of the Unix way? But I digress.
In conclusion, I claim that designing applications with small cooperating operating system processes is a good principle. Java current practice runs against this, but it need not be.
Posted in Agile, essay | No Comments »
Saturday, April 25th, 2009
A problem, and solutions that don’t seem right
I recently asked a few people to solve a little programming problem. In this problem, a number of towns are connected by one-way roads that have different distances. The programmer must write code that answers questions such as:
- What is the distance of the path A-B-C-D?
- How many paths are there from A to C that are exactly 4 steps long?
- What is the distance of the shortest path from A to D?

I reviewed three different solutions; they were all valid, reasonably well written. Two had proper unit tests, while the third was checked by prints in a "main". The authors tried hard to write a "good" solution. There were no long methods; all the logic was broken down in small methods. And yet, I was not pleased with the results.
(more…)
Posted in Expressive Code, essay | 11 Comments »
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.
Posted in essay | 5 Comments »
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ù:
Posted in essay | 6 Comments »
|
|