samedi 11 septembre 2010

Faisons plus d’interfaces fluides – puisque c’est simple!

Ca fait quelque temps que j’utilise des frameworks avec une interface fluide (cf Martin Fowler Fluent Interface). Je pense à Mockito, JMock2 et Hibernate Query API entre autres. Depuis peu j’utilise fréquemment le Test Data Builder Pattern. Ces derniers sont plutôt des Domain Specific Languages, une version évoluée des interfaces fluides. Mais je n’avais jamais réalisé que ce soit un jeu d’enfant de créer sa propre interface fluide. Prenons par exemple

Math.pow(3, 2)

Ca fait quoi au juste? Ah oui c’est l’exposant 3 de 2. Ou peut-être l’exposant 2 de 3?  Et si on écrivait plutôt



exponent(3).of(2)

Ici on voit bien ce qui se passe! Certes la javadoc de Math.pow(double a, double b) est clair et ça marche bien quant on écrit du code, mais pas quand on lit du code déjà écrit. Puisqu’on estime que du code est lu 10-100 fois plus qu’il n’est écrit (sans parler d’un framework open source), il est très important de rendre la vie facile à nos lecteurs.


Mais alors qu’est-ce ça demande comme effort de transformer Math.pow(a, b) en exponent(a).of(b)? Très peu en fait :



class Exponent {
private final double exponent;

public Exponent(double exponent) {
this.exponent = exponent;
}

public static Exponent exponent(double exponent) {
return new Exponent(exponent);
}

public double of(double base) {
return Math.pow(base,exponent);
}
}

puis un import statique dans la classe client



import static Exponent.exponent;

L’import statique avec le Factory Method exponent() jouent un rôle centrale, ils évitent le bruit syntaxique qu’il y dans



new Exponent(3).of(2)
//ou
Exponent.exponent(3).of(2)


J’ai pensé à cette astuce lorsque j’étais entrain de travailler sur un défi TDD – faire un convertisseur du système décimal vers un système quelconque. Dans ma solution il y avait cette fonction qui sert à calculer le chiffre à une position donnée dans le nouveau système. Ainsi si je veux transformer 3 en système binaire, pour connaitre si j’ai le chiffre à la position 0 (le plus à droite) je fais (3 modulo 2^1) quotient 2^0. Et pour la position 1 soit la plus à droite (3 modulo 2^2) quotient 2^1.


J’étais moyennement content de ça



private String digitAtPosition(int pos, int tenBaseNumber) {
int nextPos = pos+1;
long remainderOfNextPos = tenBaseNumber % nthPowerOfBase(nextPos);
long digitInTenBase = remainderOfNextPos / nthPowerOfBase(pos);
return convertSingleDigit(digitInTenBase);
}
protected final long nthPowerOfBase(int n) {
return round(pow(base,n));
}

on lit plus facilement



private String digitAtPosition(int pos, int tenBaseNumber) {
int nextPos = pos+1;
long remainderOfNextPos = tenBaseNumber % exponent(nextPos).of(base);
long digitInTenBase = remainderOfNextPos / exponent(pos).of(base);
return convertSingleDigit(digitInTenBase);
}

De plus cette classe extraite, Exponent, n’a rien de spécifique à mon convertisseur et pourrait potentiellement être utilisée ailleurs, promouvant ainsi la réutilisation. De plus et de manière générale une interface fluide est le début d’un DSL que l’on verrait plus facilement apparaitre à des parties du code fortement utilisés.


Et la performance?



Ca doit coûter cher de créer un objet à chaque fois, non?


Executer exonent(3).of(2) un million de fois prend 0,9s là ou Math.pow(2, 3) prend 0,64s sur mon portable, si j’augmente l’exposant à chacun des un millions d’appels (histoire de ne pas me faire avoir par une optimisation du JVM) j’obtiens 0,84s contre 1,4s. On reste tout de même à l’échelle du nano-second, alors à moins que cela soit le cœur de mon application je m’arrêtes volontiers à la deuxième étape dans ces bonnes principes



  1. Make it work


  2. Make it good


  3. (Then perhaps) Make it fast



Et si le besoin de rapidité apparait, il y a plusieurs façons d’attaquer le problème, revenir en arrière, ou introduire un cache des objets crées, ou en modifiant l’interface un poil :




exponent(3, of(2))
...

public static double of(double number) {
return number;
}



Des variantes de la classe Exponent



Pour ceux qui trouvent que la classe Exponent est trop verbeuse, il y a un peu plus bref ( en utilisant les double accolades pour accéder à l’instance initializer)



class Exponent {
double exp;
public static Exponent exponent(final double exponent) {
return new Exponent() {{
this.exp = exponent;
}};
}

public double of(double base) {
return Math.pow(base,exp);
}
}

encore une autre qui évite la variable d’instance et qui permet d’autres implementations de of() – par exemple log(logBase).of(someNumber).



class FluidMaths {
public static Exponent exponent(final int exponent) {
return new Exponent() {
public Long of(int base) {
return round(pow(base,exponent));
}
};
}

interface Exponent {
public Long of(int base);
}
}





 






  

Aucun commentaire:

Enregistrer un commentaire