Sortant d'un atelier Scala, je me replonge plein d'entrain dans le livre Programming in Scala de Martin Odersky, et je trouve une solution bête à un soucis récurrent que l'on rencontre lorqu'on loggue sans vouloir que la machine passe du temps à évaluer la chaine de caractère.

Un exemple vaut mieux qu'un long discours :

// declaration
Log logger = LogFactory.getLog(getClass());
// .... plus loin
logger.debug("je veux debuger le resultat d'une " + 
         methodeQuiPrendDuTemps()); // ne pas faire ça !

lorsque la ligne logger.debug est évaluée, le paramètre est construit avant l'appel de la méthode. Ce qui fait que même si le niveau de log est en info, la methodeQuiPrendDuTemps() va être appelée alors que la log ne sera pas visible. Pour éviter ce problème nous avions l'habitude en java de faire :

if (logger.isDebugEnabled()) {
    logger.debug("je veux debuger le resultat d'une " + 
             methodeQuiPrendDuTemps()); 
    // c'est mieux mais c'est verbeux
}

En effet, si vous avez de multiples chaînes construites avec des paramètres c'est très lourd d'échapper la log à chaque appel. Il existe pas mal de sucres syntaxiques (méthodes statiques par exemple) pour faire plus court, mais il restait de la duplication.

En scala, la possibilité de passer des paramètres "par nom" (by name parameter) permet d'éviter cela. Comme en scala on peut définir des fonctions anonymes de cette manière :

() => String

Nous avons définit une fonction anonyme qui ne prend aucun paramètre et qui renvoie une chaîne. Nous pouvons alors écrire :

def debug (constructeurDeChaine: () => String) = 
    if (logger.isDebugEnabled()) 
        logger.debug(constructeurDeChaine())

Et l'appeler comme ceci :

debug(() => "je veux debuger le resultat d'une " + methode())

La construction de la chaîne n'est appelée que si le logger est en mode debug. La chose un peu moche c'est le () => devant la chaîne. C'est pour cela que le "by-name parameter" existe en scala. On peut écrire :

def debug (constructeurDeChaine:  => String) = 
    if (logger.isDebugEnabled()) 
        logger.debug(constructeurDeChaine)

Et l'appeler simplement :

debug("je veux debuger le resultat d'une " + methode())

Il suffit ensuite de mettre les fonctions dans un trait par exemple :

trait Logging {
    Log logger = LogFactory.getLog(getClass())

    def debug (chaine:  => String) = 
        if (logger.isDebugEnabled()) logger.debug(chaine)
    
    def info (chaine:  => String) = 
        if (logger.isInfoEnabled()) logger.info(chaine)
    // etc
}

Ici, chaine est une fonction et non une chaine de caractères. Et dans une classe qui doit logguer :

class MaClasse extends BlahBlah with Logging {
    def uneMethode() = {
        // ...
        debug "je loggue facilement avec " + unContexteQuiPrendDuTemps
    }
}

On peut voir la puissance du langage en matière de chasse à la duplication et contrôles. Pour plus de précisions, se référer au chapitre 9 Control Abstraction de Programming in Scala.