Code kwaliteit

De demo van mod4j gaf mij de indruk dat het vooral bedoeld is om de java ontwikkelaar te ontlasten. Je gebruikt hier een DSL en hoeft niet meer zelf de noodzakelijke java boilerplate code te schrijven voor zaken die toch vrij standaard zijn. Een oplossing voor een typisch java probleem.

Wat ik interessant vond was de stelling dat je de kwaliteit van de code waarborgt, doordat je met een DSL minder kan: je kunt alleen binnen de kaders van het specifieke domein mee ontwikkelen. Ontwikkelaars kunnen niet helemaal wild gaan als in een multi purpose programmeertaal.

Daar kan ik me wel in vinden. Waar ik me minder in kan vinden is dat dit een oplossing is voor het gebrek aan goede programmeurs. Gesteld werd dat je nu ook met minder goede programmeurs, waar er blijkbaar veel meer van zijn, aan de slag kunt.

Maar technologie is geen vervanging voor skills of ervaring. Er zijn simpelere oplossingen om kwaliteit te waarborgen. Bijvoorbeeld de code review of de extreme versie hiervan: pair programming.
Hiermee kun je ook een consistente kwaliteit afdwingen, maar wat veel belangrijker is: je gaat het gesprek aan en je leert van elkaar.

Overigens vind ik het goed en minder goed criterium een beetje arbitrair. Door het not-invented-here syndroom heb je al gauw de neiging om iemand anders code minder goed te vinden. Kreeg van de week van een klant te horen dat we haantjes gedrag vertoonden en dat het het project geen goed had gedaan.

Eigenlijk zou je als je de beste programmeur in het team bent moeten vertrekken en zoeken naar een beter team waarin je weer de slechtste bent. Het houd je bescheiden, en je blijft door leren.

Snow Leopard upgrade perikelen

De upgrade van Leopard naar Snow Leopard ging niet zonder problemen.
    Met Java ging het mis, omdat Snow Leopard alleen Java 6.0 meelevert en ons project Java 5.0 nodig heeft.
    Intellij start default in 64 bits mode op, waar de performance aanzienlijk minder van is dan de 32 bits mode.
    Mijn UMTS dongle werkte niet meer.

Hier volgen mijn fixes. Opstarten in 32 bits mode:
start-in-32-bits

Om terug te gaan naar java5 vond ik de volgende instructies op oneswarm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd /tmp/
curl -o java.1.5.0-leopard.tar.gz http://www.cs.washington.edu/homes/isdal/snow_leopard_workaround/java.1.5.0-leopard.tar.gz
tar -xvzf java.1.5.0-leopard.tar.gz
 
#Move it to your System java folder (password needed)
sudo mv 1.5.0 /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0-leopard
 
#Tell OS X that java 5 actually is java 5
cd /System/Library/Frameworks/JavaVM.framework/Versions/
sudo rm 1.5.0
sudo ln -s 1.5.0-leopard 1.5.0
sudo rm 1.5
sudo ln -s 1.5.0 1.5
 
#Open Java Preferences
open "/Applications/Utilities/Java Preferences.app"

java_preferences

Voor de UMTS dongle moest ik nieuwe drivers installeren, nadat ik de oude drivers eerst weggehaald had. De instructies daarvoor vond ik bij edziardo:

1
2
3
4
5
6
cd /Applications
sudo rm -rf MobileConnect.app
cd  /System/Library/Extensions/
sudo rm -rf MobileConnect\ power.app/
cd /Library/Modem\ Scripts/
rm -rf HUAWEI\ Mobile\ Connect\ -\ 3G\ Modem

En daarna pas de xs4all instructie volgen.

Lessons learned in Smalltalk: Cascades

Ik werk nu een jaar professioneel als Smalltalk ontwikkelaar en het leek me wel handig om op een rijtje te zetten wat ik allemaal geleerd heb over Smalltalk. Te beginnen met cascades.

Cascades in Smalltalk zijn een beetje te vergelijken met de With statement in Visual Basic:

1
2
3
4
With aPoint
  .x = 10
  .y = 100
End With

In Smalltalk geef je met een punt komma (;) aan dat je nog een message naar dezelfde ontvanger wilt sturen. En in tegenstelling tot Visual Basic kan je de ontvanger ook teruggeven, want handig is bij het initializeren van een object:

1
aPoint := (Point new) x: 10; y: 100.

Je hebt ook minder tijdelijke variabelen nodig hebt. Het voorbeeld van Martin Fowler over fluent interfaces;

1
2
3
4
5
6
7
8
9
10
11
12
private void makeNormal(Customer customer) {
  Order o1 = new Order();
  customer.addOrder(o1);
  OrderLine line1 = new OrderLine(6, Product.find("TAL"));
  o1.addLine(line1);
  OrderLine line2 = new OrderLine(5, Product.find("HPK"));
  o1.addLine(line2);
  OrderLine line3 = new OrderLine(3, Product.find("LGV"));
  o1.addLine(line3);
  line2.setSkippable(true);
  o1.setRush(true);
}

kunnen we in Smalltalk schrijven als:

1
2
3
4
5
6
7
8
makeNormal: customer
 
  customer addOrder:
    (Order new)
      addLine: (OrderLine  withAmount: 6; product: (Product find: 'TAL'));
      addLine: ((OrderLine  withAmount: 5; product: (Product find: 'HPK')) skippable: true);
      addLine: (OrderLine  withAmount: 3; product: (Product find: 'LGV'));
      rush: true.

En dan komen we al aardig in de richting van de fluent interface oplossing van Fowler.

1
2
3
4
5
6
7
private void makeFluent(Customer customer) {
  customer.newOrder()
  .with(6, "TAL")
  .with(5, "HPK").skippable()
  .with(3, "LGV")
  .priorityRush();
}

Ik kan de Smalltalk code nog verder verfijnen en tot een simpele interne DSL komen. Het blijft dan nog steeds Smalltalk en gaat niet tegen de natuur van de taal in zoals de Java fluent interface oplossing.

Het gebruik van cascades is als pattern opgenomen in Smalltalk Best Practice Patterns van Kent Beck. Hij geeft aan een cascade alleen te gebruiken als de messages bij elkaar horen en niet als ze toevallig naar dezelfde ontvanger verstuurd moeten worden.

Array literals in Java

Tijdens een smalltalk code review werd ik gewezen op mijn gebruik van OR:

1
(value = #one) :or (value = #two) ifTrue: [doIt]

Een betere manier is om een array literal te creeren en dan includes te gebruiken:

1
(#(#one #two) includes: value) ifTrue: [doIt]

In java is de OR versie als volgt:

1
2
3
if  ("one".equals(value) || "two".equals(value)	) {
   doIt();
}

Er is niet zo een simpele manier om arrays in java te definieren, maar wel een array list:

1
2
3
if (Arrays.asList("one", "two").contains(value)) {
 doIt();
}

Met een static import:

1
2
3
if (asList("one", "two").contains(value)) {
  doIt();
}

De array list oplossing kost wel wat performance, maar het is in mijn ogen leesbaarder, zeker als je nog meer cases toevoegt. En met een een introduce variable refactoring kun je het zelfs nog duidelijker maken:

1
2
3
4
String[] numbersToProcess = asList("one", "two");
if (numbersToProcess.contains(value)) {
  doIt();
}

Probeer eens de versie met || luidop te lezen en vervolgens de laatste versie.

Named arguments in Java

Named arguments maken je code beter leesbaarder. In Visual Basic kon het, in Ruby kun je het met een hash en in Smalltalk is het gewoon verplicht. In de volgende versie's van C# en Scala komt het ook. In Java zal het wel nooit komen, maar er is wel een manier om het te simuleren in Java.

Wat is het nut van named arguments? Neem als voorbeeld de volgende methode.

1
public void setMargin(int top, int bottom, int left, int right){..};

Bij de aanroep krijg je iets van:

1
setMargins(2,1,2,1);

Zonder de method signature te kennen weet ik niet precies wat de code doet. Met named arguments heb je volgende code:

setMargins(top:=2, bottom:=1, left:=2, right:=1);

Ik hoef nooit te kijken naar de method signature of het uit mijn hoofd te kennen om te begrijpen wat er staat. Maar hoe kunnen we het doen in java. Je kunt simpele syntactic sugar gebruiken door static wrapper methodes te gebruiken:

1
2
3
4
5
6
int top(int i) {return i}
int left(int i) {return i}
int bottom(int i) {return i}
int right(int i) {return i}
 
setMargins(top(2), bottom(1), left(2), right(1));

Dit maakt de code wat leesbaarder, maar het is niet meer dan wat commentaar. En net zoals met commentaar weet je nooit zeker of het echt klopt. Het is nog steeds mogelijk om de volgorde te wisselen en de lezer op een verkeerd spoor te zetten.

Maar wat als elk argument een eigen type heeft i.p.v. een primitive:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void setMargin(TopMargin top, BottomMargin bottom, LeftMargin left, RightMargin right);
 
static TopMargin top(int i) {return new TopMargin(i)}; 
static BottomMargin bottom(int i) {return new BottomMargin(i)}; 
static LeftMargin left(int i) {return new LeftMargin(i)};
static RightMargin right(int i) {return new RightMargin(i)};
 
class TopMargin{
  int value;
  TopMargin(int i){
    value := i;
  }
 
  int getValue(){
    return i;
  }
}

Het definieren van de classes is wel veel extra werk, maar dit kun je via een template in je IDE laten genereren. Het blijft wel extra code waarin fouten kunnen sluipen. Doordat je constructors niet kunt overerven in Java zal een Margin superclass niet veel helpen in het reduceren van de code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Margin{
  int value;
  Margin(int i){
    value := i;
  }
 
  int getValue(){
    return i;
  }
}
 
class TopMargin extends Margin {
  TopMargin(i){
    super(i);
  }
}

Ik heb deze techniek gebruikt bij het schrijven van JUnit testcases. Ik had daar veel literals als input, maar het was niet goed duidelijk of ik het had over een aantal, een prijs of over kosten.

1
2
buyPhilips(100, 100, 10);
p.setCurrentQuote(createQuote(philips(), 200, 100, 100));

werd dan:

1
2
buyPhilips(amount(100), price(100), costs(10));
p.setCurrentQuote(createQuote(philips(), price(200), lastYearPrice(100), lastQuarterPrice(100)));

Het is nu duidelijk waar de argumenten voor staan en het is ook typesafe.

I Don’t Care

Ik was even bezig met wat javascript en een collega maakte weer een negatieve opmerking over dynamische talen. Ik heb niet zoveel zin in weer een discussie over static vs dynamic.

Ik heb de afgelopen maanden behoorlijk wat static typed Java code geschreven en ga de komende maanden heel veel dynamic typed Smalltalk code schrijven. Ik doe gewoon beide, punt.
Het is ook een zinloze discussie. De een voelt zich beter bij een bepaald paradigma, de ander weer bij een ander. Het is niet belangrijk welke technologie beter is of welke wint.

Er was een tijd dat het wel belangrijk was, omdat de kans dat je met de "winnende" technologie zou moeten werken vrij groot is. De markt blijk echter groot genoeg voor meerdere "winnaars". En als ontwikkelaar kan ik kiezen waar ik mijn brood mee wil verdienen. En ik kies nu voor het obscure en dood gewaande Smalltalk.

Utility classes en default constructors

Checkstyle heeft standaard een warning als je een utility class, een class met alleen static methods, hebt waarvan je een nutteloze instantie kan maken. De oplossing is dan om een private default constructor te maken. Ja, java kent geen modules dus de "nette" manier om van een class een module te maken is door er een stukje "unreachable" code in te plaatsen. Alsof je niet al voldoende code moet lezen in java.

Anyway, ik doe daar niet aan mee. Ik ga ervan uit dat degene die mijn utility class gebruikt, capabel genoeg is om te zien dat een instantie ervan nutteloos is. Ik bedoel, wat kan er fout gaan? Je maakt een instantie, en vervolgens merk je dat je er niks mee kan. Voordat je op de compile knop drukt heb je de code al weggehaald en de kans dat het in productie code komt is nihil. Het beschermen van de programmeur tegen zichzelf heeft java al lastig genoeg gemaakt (zoals checked exceptions).

Wat nog wel een ander probleem was met een private constructor voor een utility class, is dat cobertura wel de constructor ziet en vind dat deze niet getest is. Je Code Coverage er mee omlaag gaat, en als je op 100% zit is het zonde om dan weer terug te gaan naar 99%. Dan valt de echte niet geteste code minder op.

Maar als je toch de private constructor wilt gebruiken kun je met reflectie toch nog op 100% code coverage komen.

1
2
3
4
5
6
7
8
9
10
11
12
public void testPrivateConstructor() throws
         IllegalAccessException,
         InvocationTargetException,
         InstantiationException {
 
    final Class<?> aClass = Environment.class;
    final Constructor<?> constructor = aClass.getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    final Object n = constructor.newInstance((Object[]) null);
    assertEquals("should have environment instance", 
                          Environment.class, n.getClass());        
}

Of je zet nog meer onzin code in de productie code door een static initializer toe te passen:

1
2
3
{
   new Environment();
}