Un altro Agile Day è passato

November 24th, 2008

Un altro Agile Day è passato.

Tutto di corsa

Purtroppo quest’anno non ho assistito a molte sessioni; sono partito da Amsterdam con l’aereo delle 7, e sono riuscito ad arrivare a Bologna verso le 11:30, quindi dopo la sessione di apertura e il panel. Poi quando sono arrivato, mi ha fatto un enorme piacere rivedere tante persone che non vedevo da un po’, quindi ho passato un po’ di tempo a chiacchierare e un po’ di tempo a completare le slide della mia presentazione. Come risultato, al mattino non ho seguito alcuna sessione.

Retrospective, Standup and Journal

Al pomeriggio ho seguito la presentazione di Piero Di Bello e Jacopo Franzoi su “Retrospective, Standup e Journal”. Si parlava di queste tre pratiche meno “tecniche” ma di enorme importanza nella vita del team. Se uno vuole rendere agile un team, e dovesse scegliere di iniziare con una sola pratica, farebbe bene a iniziare con le retrospettive. Perché è nella retrospettiva che il team inizia il processo di miglioramento continuo, che è uno dei due obiettivi dell’agilità. (L’altro è “soddisfare il cliente con rilasci rapidi e continui di software che ha valore.”) Il libro di riferimento sulle retrospettive è sempre quello di Derby e Larsen, Agile Retrospectives.

Piero e Jacopo non si sono limitati a raccontare quello che c’è nel libro; hanno raccontato episodi, pattern e antipattern vissuti nella loro esperienza di mentori, di cui ho parlato nella sessione successiva io. Esempi di antipattern: quando non si parla (il “silenzio degli innocenti”), quando si parla troppo, e conviene rimandare le discussioni tecniche in altra sede; quando si parla delle stesse cose, e conviene cercare di smuovere lo status quo variando il formato della retrospettiva, oppure facendola meno spesso.

Lo stand-up meeting è un’altro momento fondamentale nella vita del team. E’ così “facile” implementarlo che si dà per scontato che tutti lo facciano bene, ma non è così. Anche qui bisogna stare attenti ad alcune cose, di cui secondo me la più importante è che l’orario deve essere fissato e si deve assolutamente iniziare all’ora stabilita, anche se manca qualcuno. Anche di questa pratica Piero e Jacopo hanno raccontato un po’ della loro esperienza vissuta.

Infine hanno parlato del journal, ovvero il diario del team. Questa è una pratica di cui non si parla molto in letteratura. Piero e Jacopo l’hanno appresa lavorando nel team XPlayers in Quinary. (Oggi lavorano con me nel team Orione in Sourcesense.) Nel nostro team il diario viene tenuto regolarmente. E’ un’ottima maniera di tenersi aggiornati su quello che succede nel team, di riflettere a fine giornata su quello che si è fatto, e dà uno spunto al mattino per lo stand-up meeting.

La nostra esperienza di mentoring

Poi ho fatto la mia sessione, in cui ho raccontato l’esperienza degli ultimi 9 mesi del team Orione. Abbiamo fatto due episodi di mentoring, in cui una parte del nostro team ha affiancato il team del cliente, allo scopo di trasmettere il metodo XP lavorando su un progetto vero del cliente. I due episodi sono stati molto diversi; nel primo avevamo un team di 4 sviluppatori rumeni, dipendenti di una banca italiana. Nel secondo avevamo un team di circa 15 sviluppatori per lo più italiani, che erano stati arruolati in “time & material” da un’importante istituzione finanziaria italo-britannica per un progetto web. Mentre nel primo caso gli orionisti erano circa metà del totale, nel secondo eravamo in netta minoranza. La profondità della trasmissione del metodo ne risente; puoi agilizzare poche persone in profondità, oppure tante in maniera più superficiale. Questa è una regola generale del mentoring: la tua capacità è limitata, se la rivolgi a più persone sarà più sottile.

Ciononostante, l’applicazione di alcune pratiche chiave del metodo agile ha portato dei grossi benefici anche per questo cliente. Abbiamo aiutato a creare un team coeso; a tracciare e pianificare in maniera realistica il progetto, fornendo numeri affidabili per poter prendere decisioni importanti in maniera tempestiva; a migliorare la qualità tecnica del software prodotto.

Sono molto d’accordo con i punti che Gabrielle Benefield ha riportato dalla sua esperienza della agilizzazione di Yahoo, che ho letto sul blog di Pascal:

  • Go deep Agile with a few of the most important teams, rather than spread Agile thinly over a lot of teams.
  • Technical excellence, attention to quality and good engineering practices are essential.
  • Grow slowly with teams that volunteer. Don’t overstretch your coach capacity.
  • Involve management. Inform, address fears and explain “what’s in it for them”.
  • There will always be people who don’t like or want Agile.
  • Don’t just change the process; change the structure of the company.
  • Bribe people with snacks.

Processo al database relazionale

Il mitico XP User Group di Bologna ha presentato in forma di pantomima un argomento molto interessante: il Processo al database relazionale. Con carisma di attori consumati, hanno portato in scena l’imputato (sotto forma di un cilindro di polistirolo, come viene rappresentato negli schizzi), il giudice Roberto Bertazzoni, il pubblico ministero Paolo Perrotta, l’avvocato difensore Finelli, e due testimoni! I testimoni sono stati interrogati dalle due parti, e hanno raccontato tristi storie di stored procedure lunghe 30 pagine (in Monaco 10), o di stored procedure che prelevano frammenti di query da una tabella di query (!!!). L’avvocato dell’accusa sembrava avere gioco facile nel dimostrare a quali disastri architetturali possa portare l’uso dissennato del database, ma l’avvocato difensore ha giustamente puntualizzato che lo strumento non ha colpa del fatto di essere utilizzato male; e che comunque i due sistemi in questione funzionano bene, e se l’imputato Sig. Relazionale Database non ci fosse, sarebbe forse più difficile ottenere gli stessi risultati.

L’arringa dell’accusa (introdotta da una slide che rappresentava un aringa), è stata molto convincente, con un esilarante citazione dal geek padre di noi tutti, Leonardo Da Vinci, e dei suoi consigli sull’architettura delle applicazioni. Paolo, voglio stamparmi tutto lo scritto leonardesco su una maglietta!! Il succo: separate le applicazioni nei tre laièri, ovvero strati, e mantenete l’intelligenza fuori dallo strato di persistenza.

L’arringa della difesa è stata pure molto convincente. Usate lo strumento nella maniera appropriata. Non ha fatto uso però di un argomento che per me è decisivo, e cioè: se non ci fosse il Sig. Relazionale a gestire l’accesso concorrente ai dati, saremmo nella cacca. Ci toccherebbe trafficare con semafori, thread e mutex, il che secondo me, nella programmazione applicativa, è un gravissimo antipattern. Non però nella programmazione di sistema, che è poi quella che si occupa di costruire strumenti come il Sig. Relazionale.

Alla fine l’imputato è stato dichiarato colpevole dalla giuria (il pubblico) di due su tre dei capi d’accusa.

Mi tolgo il cappello di fronte a uno user group che è capace di a) discutere di argomenti tecnici con passione fino alle due di notte, e b) realizzare una simile messa in scena.

Retrospettiva italiana

Luca Minudel ha condotto la retrospettiva italiana, in cui quelli del pubblico che applicano in pratica un metodo agile sono stati invitati a raccontare una cosa buona e una cattiva che è capitata in quest’ultimo anno. Abbiamo lavato un po’ di panni sporchi in pubblico, cosa che al pubblico magari interessava poco. Abbiamo parlato anche di azioni che possiamo fare per migliorare la qualità della comunità agile italiana.

Ci sono state diverse proposte; la migliore è stata quella di Tommaso che ha proposto un check-up fra due mesi: il 31 gennaio tutti noi siamo chiamati a scrivere un messaggio sulla mailing list extremeprogramming-it, per raccontare in che cosa è migliorata la nostra attività lavorativa dal 21 novembre. Questa proposta mi piace perché: è semplice, attuabile, è un piccolo passo, ed è efficace; realizza un proseguimento della retrospettiva italiana. Risponde anche al problema percepito da Simone che diceva che la mailing list è un po’ smorta (davvero? a me pare così trafficata che fatico a seguire tutto.)

La mia proposta era di proporre in Italia un numero maggiore di workshop di formazione, del tipo di quello di Tommaso e Fabiana sul refactoring, che è andato completo in pochi secondi da quando è stato reclamizzato. E’ evidente che c’è fame di questo tipo di eventi. Io proporrei di fare una giornata dedicata ai workshop (ovvero a seminari di formazione pratici, dedicati a un numero limitato, tipo 20 persone.) Si potrebbe fare o attaccata all’Agile Day dell’anno prossimo, oppure separata di circa sei mesi. Purtroppo Marco Abis non è stato presente alla retrospettiva. Mi sarebbe piaciuto sentire il suo parere.

Conclusione…

Mi dolgo di non avere partecipato alla canonica cena organizzata dagli amici del Bologna XPUG. Peggio per me! Sono felice di avere partecipato al quinto Agile Day, di avere rivisto tutti, e di avere portato un contributo. Sono felice che il mio intervento sia stato apprezzato da tante persone. Sono orgoglioso che tanti del mio team abbiano portato presentazioni o workshop.

Grazie a Marco, e tutti quanti hanno partecipato. Cerchiamo di mantenere viva quest’energia e di migliorare sempre!

Che cos’è un test di accettazione?

November 22nd, 2008

Jacopo chiede:

cos’è un test di accettazione? e uno di integrazione? usiamo i sistemi veri o quelli finti?

La mia risposta: Un test di accettazione ha lo scopo di dimostrare che abbiamo capito tutto quello che c’è da fare per una storia d’uso. Il test d’accettazione serve per

  1. dimostrare al cliente che abbiamo capito quello che chiede
  2. chiedere conferma al cliente se abbiamo capito quello che chiede
  3. nel processo di fare le ultime due cose, avviare un dialogo costruttivo sul cliente basato su *esempi* di come deve comportarsi il sistema.

Per fare questo, non è necessario passare per l’interfaccia utente e nemmeno usare i sistemi veri; ci si può semplicemente collegare direttamente alle classi di dominio che fanno il ragionamento.

One of the most common mistakes in creating customer tests is describing what happens in the user interface rather than providing examples of business rules. … Good examples focus on the essence of your rules. Rather than imagining how these rules might work in an application, think about what your rules are. If you weren’t creating an application, how would you describe those rules to a collegue? Talk about things rather than actions. … It takes a bit of practice to learn to think this way, but the results are worth it. The tests become more compact, easier to maintain, and … faster to run.

Shore and Warden, The Art of Agile Development, p. 285.

Quindi un test di accettazione NON E’ UNA DEMO!

La demo deve essere il più possibile interattiva. Per fare una demo è utile avere una interfaccia utente con cui giocare. Se il progetto è batch, conviene creare una mini-interfaccia molto semplice, magari basata su riga di comando, per stimolare il sistema. Perché i nostri test di accettazione stimolano il sistema su un insieme di esempi fissato. Invece durante la demo, e durante il test esplorativo, è importante essere in grado di provare cose diverse che ti vengono in mente e vedere rapidamente come reagisce il sistema.

Philosophically, exploratory testing is similar to test-driven development and incremental design: rather than designing a huge suite of tests up-front, you design a single test in your head, execute it against the software, and see what happens. Ther result of each test leads you to design the next.

Shore and Warden, The Art of Agile Development, p. 345.

Un test di integrazione, invece è un test che prova che il nostro sistema interagisce correttamente con *un* sottosistema esterno. Se sviluppi secondo l’architettura esagonale, il test di accettazione verifica l’implementazione corretta di *un* lato dell’esagono.

Un test di integrazione end-to-end, invece, testa il sistema tutto, con i suoi collaboratori esterni, dall’interfaccia utente. In molti casi il test end-to-end non conviene; perché è lento, fragile e difficile da manutenere. I test di integrazione focalizzati, insieme ai test unitari e ai test di integrazione, sono di solito sufficienti a darmi una buona confidenza sulla qualità del mio sistema. Ma soprattutto, il test di integrazione end-to-end non è efficace come il test esplorativo. Magari il test end-to-end è verde, ma appena provo a usare il sistema in maniera interattiva va tutto a pezzi.

Instead of end-to-end tests, use exploratory testing to check the effectiveness of your unit and integration tests. When your exploratory tests find a problem, use that information to improve your approach to unit and integration testing, rather than introduce end-to-end tests.

Shore and Warden, The Art of Agile Development, p. 305.

Pair programming antipattern: discussing design

November 19th, 2008

There’s a place to discuss design, but a pair programming session is not it. Pair programming is about programming. Sometimes I see developers stop typing and debating endlessly on how to do things. Sometimes, when I do pair programming (yes, I still get to do some coding sometimes) I hear my pair stopping me with objections, usually about design. “You’re going to introduce an IF!”. Yes. Definitely. I know that. But I’m not going to discuss that with you right now!

When I have a good idea of how to progress, I just do it. There’s no need to stop me to discuss my style; that will only take away energy from me. Let the code speak: if my idea is right, the pieces will fall together nicely and then we’ll decide if it’s worth introducing more design to get rid of the IF. If my idea is not so good, then the code will speak by itself. Suppose the pomodoro rings, and we realize I’m going nowhere; when we start the next pomodoro we toss my code, start afresh, and try some other way.

Maybe my pair thinks I’m going nowhere. If she’s quick enough to grab the keyboard from me, she can try another way. Let the code speak!

This does not, I repeat does not mean you should treat your pair disrespectfully. It is a completely different matter when my pair does not understand what I’m doing. It’s my responsibility to explain, with clarity and patience, what I’m trying to do. But! If my pair thinks he understand but wishes I tried some other way, then there’s two possibilities: either he will be patient and wait to see if I’m getting somewhere; or he’ll grab the keyboard from me and try another way.

There’s a place for design discussions; the best place in my opinion is in front of the whiteboard (and maybe with a cup of tea in hand.) Design discussions are good to clarify longer term goals and issues; or to explain and reason about domain difficulties. But when I’m at the keyboard, then the code is my whiteboard. Let the code speak!


The idea of “grabbing the keyboard” I learned from Francesco Cirillo (good luck trying to grab the keyboard from him!) See also his post Mamma Programming.

Un’incontro per i dirigenti IT

November 19th, 2008

Credo che oggi tutti i manager dell’IT, se non hanno tenuto di proposito la testa sotto la sabbia negli ultimi 9 anni, abbiano sentito parlare in abbondanza dei “Metodi Agili”. Questi metodi nel nostro Paese non sono ancora diffusi come nel mondo anglosassone; quindi i metodi agili sono conosciuti dai più per sentito dire; non tutti hanno avuto l’occasione di incontrare persone che li applicano veramente. Inoltre gli agilisti sono per lo più sviluppatori, e quando organizzano (organizziamo) un incontro, di solito è dedicato ad altri sviluppatori.

Ora, il problema è che è fin troppo facile avere una percezione inesatta, o anche distorta, di che cosa siano i metodi agili, e di perché sono importanti per tutte le persone che tengono al successo dei propri progetti software. Anche e soprattutto i manager. Per questo Sourcesense organizza un incontro dedicato ai manager IT.

Il 25 novembre si terrà a Milano una colazione di lavoro in cui Sourcesense, in partnership con Red Hat, introdurrà l’argomento Metodi Agili. In quell’occasione avrò modo di spiegare ai manager presenti che cosa intendo io per Metodi Agili, e perché sono importanti. Le mie credenziali, per parlare di questo argomento, sono il lavoro di coaching, mentoring e training che ho fatto negli ultimi 3 anni.

Per partecipare, basta visitare la pagina dedicata all’incontro e mandare la richiesta di partecipazione.

Project tracking is a lie (in the waterfall)

November 5th, 2008

Some time ago Dave Nicolette expressed so well something that I was aware of but could not formulate clearly. That is, in traditional waterfall projects the percent-completes are a lie; all they tell you is how much time is passed since the time the project started, and that is the only metric that a traditional project manager, armed with his shining copy of Microsoft Project, is tracking.

So please jump to Dave’s blog post and read about it!

Java anti-pattern: splitting the project into multiple projects

November 1st, 2008

Don’t split your project into multiple separate projects unless absolutely necessary. By this I mean, don’t have a separate “model” project that produces a project-model.jar, an “infrastructure” project that contains data-access objects, a “web application” project that contains the user interface of the application, and so on. This breaking down the project in subprojects

  1. increases complexity a lot,
  2. makes integration longer and more difficult,
  3. is an obstacle to the evolution of the design,
  4. and results in a lot of work being spent on the build system.

With multiple projects it happens that you do work in one project, and the other projects don’t “see” your latest modifications. Perhaps your application works properly when tested within Eclipse, but fails to compile in the command line, because some of the jars are not up-to-date. This sort of things drives people to introduce things like Maven or Ivy in the project, and that brings a lot of additional complexity. Maven and Ivy are very complex tools. They tend to look for jars all over the world at every build, making integration slow, difficult to understand and unreliable. I’ve seen project that require from one to two hours just for integrating your changes into the repo, and that assuming that you’re skilled in working with the build process. All of this has nothing to do with producing value for the customer.

It’s just too easy to spend lots of time building complex infrastructure. Don’t do that: spend time thinking how to simplify things instead. Think about how to produce value for the customer. Think about how to obtain the desired result with less complexity.

Tip: instead of splitting your project, keep all your code in a single project. Packages and source folders are the proper tools for keeping layers and modules separate.

Within the multiple-projects antipattern, there’s a second, minor antipattern, that is the trunk, tags and branches folders everywhere antipattern. By this I mean that each and every one of your sub-projects has its own triad of trunk, tags and branches directories. Even assuming that we’re going to need these three directories, they should appear only once, at the very top of the tree of subprojects. It you do it like this, the triad will not get in the way: I can checkout the trunk once, and I will have a single, complete and consistent tree to work with. If every project has its own separate triad, there’s no easy way to manipulate the whole tree of projects without including tags and branches. No easy checkout, no easy update, etc.

Tip: avoid the “trunk, branches, tags” triad if you can. But if you must have them, put them at the very top of your source tree; never work with more than one triad.

Now I hear you ask: “But what if I cannot keep all of my code in a single project? What if I have to produce two versions of my application? What if I have to produce more than one application sharing most of the codebase?”

Even so, dear reader, my advice does not change. I would generate multiple apps from the same single project. I would write a separate ant task to build each version of the app; my ant tasks would pick and choose the pieces that are needed for a particular app. Very simple. I know that this goes against the pattern “one artifact per project” pattern. So be it; I don’t think it’s a particularly valuable pattern, expecially since it leads the way to the introduction of complex build systems like Maven.

“But you’re going to end up with a messy, complicated, unreadable ant buildfile!” you say. I don’t think so, dear reader. There is a danger of making a mess of the ant buildfiles, but then again this is the usual danger of making a mess of your software. The same care and skill that is required to write simple, high quality source code must be applied to the buildfiles. In my experience, with some care, I can keep my ant buildfiles pretty lean.

“But I have a legacy system, full of separate projects!” In that and other cases, when it’s impossible to keep all the code in a single project, you can get by with a relatively simple buildfile. All you need to do in the buildfile is to explicitly invoke the build on all the projects it depends on. This will increase the complexity of your buildfiles, but not dramatically so. It’s made easier if you can rely on all of the subprojects being at predictable positions within your working directory. Otherwise, you can cope by defining environment variables or per-developer configuration files that help ant find the projects it depends on.

“But my situation is different! I need Maven!” Dear reader, I won’t tell you what you need in your situation. You are the only person in a position to know that. This advice worked for me. My bet is that in almost every situation Maven or Ivy could be profitably replaced by a simpler scheme, if you really care about keeping complexity down, and are willing to spend time and thought for that.

Lessons learned on writing acceptance tests

October 14th, 2008

Yesterday I was working with Jacopo on finishing up the acceptance tests for a story. The story was something like “as a user, I want to see a page with a list of the email alerts I subscribed to.” The test looked like this:


@Test
public void shouldShowAlerts() throws Exception {
    Date today = new Date();
    String vodafone6wayKey = "...";
    alertEngineNewsBroker.store(new IndividualCompanyRule(...));
    
    WebResponse response = getResponse("/xxx/alerts/all.html");
    
    WebTable details = response.getTableWithID("details");
    assertEquals("Type", details.getCellAsText(0, 0));
    assertEquals("Name", details.getCellAsText(0, 2));
    assertEquals("Details", details.getCellAsText(0, 3));
    
    assertEquals("News", details.getCellAsText(1, 0)); //type
    assertTrue(isSameDay(today, dateFrom(details.getCellAsText(1, 1))));
    assertEquals("VODAFONE GRP.", details.getCellAsText(1, 2));
    assertEquals("All news stories", details.getCellAsText(1, 3));
}    

This acceptance test was going to be implemented with an end-to-end test running through the http interface of the web application. But I also saw that my collegue had this table sketched on his copybook.

Sketch of AT Table

The sketch was written during a conversation with the analyst, who in this project acts as the customer role. It represents the text labels that the analyst expects to see in the various cases.

It was going to be difficult to extend the above test to cover all the cases. Also, the shape of the test was wildly different from the concepts that the sketch table makes so clear. We thought about how we would set up this acceptance test in Fitnesse, which we are not using in this project. From there we got the inspiration to write the test like this:


@Test
public void shouldShowAlertNames() throws Exception {
    assertNameIs("VODAFONE GRP.", new IndividualCompanyRule(...));
    assertNameIs("ALL", new AllCompaniesRule(...));
    assertNameIs("FTSE 100", new IndexRule(...));
    assertNameIs("General Financial", new SectorRule(...));
    assertNameIs("DELL INC", new NotListedCompanyRule(...));
    assertNameIs("Nome di watchlist", new WatchlistRule(...));
    assertNameIs("Nome di portfolio", new PortfolioRule(...));
}

Now the test, while still containing a lot of technical clutter, has roughly the same shape of the sketch. All that remains is to implement the custom assertion, assertNameIs. One option was to call the code from the first test; but going through http and html seems a useless detour, when all we care about is the text displayed in different cases. Why not hook directly into the business logic that produces the label? This way the test is much more focused; and fast too.

We had unit tests already for that business logic; we agreed to have a bit of duplication in the tests, because the way the AT was written was more communicative in the eyes of the customer.

Jacopo was not happy, though, that we were ditching the end-to-end test completely. This way there was no test that shows that our page shows up at all. We agreed that we would keep one end-to-end test: no need to test all possible cases, since that is already covered in the previous test.

Lessons learned:

  • When the business logic is complex enough that you must write down details and lists of cases, it’s a good idea to write a test explicitly for testing the business logic.
  • Acceptance tests work better when they are clear and uncluttered, focused on the aspect of the story that we want to demonstrate to the customer. Fitnesse-style tables and custom assertions are useful tools here.
  • End-to-end integration tests can take an awful lot of time to write. Shore and Warden suggest to avoid them, relying instead on what they call focused integration tests, that is, tests that check the interaction with a single external system, rather than a complete tour from GUI to DB and back. Shore says that it’s more productive to test the UI interactively (which you have to do anyway) and uncover with exploratory tests any holes that your focused integration tests don’t cover. Then you refine your focused integration tests. Koskela says that using end-to-end test, or going divide-and-conquer with focused integration tests, is a judgment call: “it depends”.
  • Always time-box your activities. If you expected to spend, say, 4 pomodori on implementing ATs, and after that time you are still far from done, don’t just plow ahead and take as much time as it will ever take. Stop and reflect, ask for help, and come up with a simpler way. Do ask for help; and start over, trying to get the same value in a simpler way. See this from Francesco Cirillo: Quando la coppia scoppia (in Italian).

La lavagna del team Orione, ottobre 2008

October 7th, 2008

La lavagna del team Orione, ottobre 2008

Questa è un istantanea della lavagna che il nostro team sta usando per organizzare il lavoro. E’ diversa da come era un mese fa e probabilmente sarà diversa fra un mese. Il bello di usare strumenti semplici come lavagna, pennarelli e cartoncini, piuttosto che software monolitici e complicati, è che puoi cambiare il modo in cui li usi a seconda delle esigenze del tuo progetto e della tua capacità di organizzarti.

Another look at the anti-IF campaign

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

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ù: