Variabelen niet initialiseren bij definiëren

Ik was onlangs een aantal keer verbaasd over code van een aantal java programmeurs met vele jaren ervaring. De code die ik zag werkte prima en was ook nog goed te lezen voor iemand die de materie begreep. Maar toen ik die code moest veranderen, ging dat toch niet gemakkelijk. Voor een van deze methodes vond ik de oplossing in een los en simpel software pattern dat eigenlijk iedereen, voor praktisch elk algoritme, zou moeten gebruiken: verzamel, bewerk en sla op. In dit artikel toon ik een voorbeeld van dit pattern waarbij tegelijk een bijzondere vorm van defensief programmeren tevoorschijn komt: niet initialiseren bij definiëren.


De methode die ik moest veranderen bestaat uit ongeveer 60 regels. Het berekent ongeveer 10 getallen uit een ongeveer even groot aantal parameters volgens bizarre, maar goed omschreven business regels. Het resultaat wordt gegeven in een value object. Ongeveer zoiets, maar dan 4 keer langer:

public AntwoordenValueObject berekenAntwoorden(BigDecimal p1, BigDecimal p2, ... t/m p10) {
    AntwoordenValueObject antwoorden = new AntwoordenValueObject();
    if (p1 != null && p2 != null) {
        antwoorden.setAntw1(p1.add(p2));
        if (p2 != null) {
            antwoorden.setAntw2(antwoorden.getAntw1().add(p2));
        } else {
            antwoorden.setAntw2(antwoorden.getAntw1());
        }
    } else {
        antwoorden.setAntw1(BigDecimal.valueOf(0L));
    }
    // etc. etc.
    return antwoorden;
}

Na de refactoring ziet het er zo uit:

public AntwoordenValueObject berekenAntwoorden(BigDecimal p1, BigDecimal p2, ... t/m p10) {
    BigDecimal antw1;
    BigDecimal antw2;
    if (p1 != null && p2 != null) {
        antw1 = p1.add(p2);
        if (p2 != null) {
            antw2 = antw1.add(p2);
        } else {
            antw2 = antw1;
        }
    } else {
        antw1 = BigDecimal.valueOf(0L);
        antw2 = null;
    }
    // etc. etc.
    AntwoordenValueObject antwoorden = new AntwoordenValueObject();
    antwoorden.setAntw1(antw1);
    antwoorden.setAntw2(antw2);
    return antwoorden;
}

We kunnen een aantal opmerking plaatsen bij de nieuwe code:

  • De nieuwe code is bijna uniek aan de eerste versie, we hebben dus een grote zekerheid dat de refactoring geen nieuwe fouten heeft geïntroduceerd.
  • Het aantal karakters in het gedeelte met business regels is aanzienlijk afgenomen, dit komt de leesbaarheid en dus de onderhoudbaarheid sterk ten goed.
  • De locale variabelen worden expres niet geïnitialiseerd bij de declaratie. De compiler controleert nu voor ons bij elk executie pad of de locale variabelen worden geïnitialiseerd voordat ze worden gebruikt. Eventuele fouten of lacunes in het algoritme, of in de implementatie, komen op deze manier vanzelf naar boven. Had jij gezien dat in de eerste code antw2 niet in alle gevallen expliciet een waarde kreeg?

We hebben nu een goede basis om veranderingen aan de bussiness regels te implementeren.

De laatste truc, de compiler gebruiken om fouten in een algoritme te vinden, wordt volgens mij te weinig gebruikt. De volgende keer dat je een locale variabele declareert, overweeg dan eens de initialisatie uit te stellen en kijk wat er gebeurd.

Happy coding,
Erik van Oosten.

PS. Ernst-Jan stuurde onlangs een erg interessant artikel over het gebruik van het final keyword (http://renaud.waldura.com/doc/java/final-keyword.shtml#fields). In dit artikel wordt geheel correct aangegeven dat je fields juist wel moet initialiseren. Pas de in dit omschreven techniek dan ook alleen toe op lokale variabelen.


Reageer

RSS feed for comments on this post · TrackBack URI