Instrumentation (journalisation) des expressions Scala

( UPD après @ krivachy.akos)

Comment déboguer des expressions dans Scala? Vous n'avez pas la possibilité de définir un point d'arrêt et de voir les variables locales dans la plupart des cas car dans l'expression il n'y a pas de variables. Et généralement, il n'y a aucune instruction à laquelle vous pouvez définir un point d'arrêt.

Une ancienne façon de déboguer est d'avoir le code instrumenté. Cela donne une information indispensable sur le traitement interne des expressions.

Toutefois, dans une implémentation de journalisation standard, il n'existe aucun moyen direct d'intercepter des expressions. En particulier, un enregistreur type possède des méthodes avec un type de retour Unit :

def debug(msg: =>String) {...}

Pour utiliser le logger, il faut réécrire l'expression concise de manière à pouvoir appeler le logger:

Example 1:

si vous avez des règles booléennes avec des conditions complexes et plusieurs chemins d'évaluation:

val x = if(condition1(a,b)) 
          Some(production1(a,b))
        else if(condition2(c,d))
          Some(production2(a,b))
        else
          None

alors il est difficile de s'assurer que cela fonctionne comme souhaité. (Il n'est pas toujours possible d'éviter complètement les règles complexes et les règles de représentation dans le style POO ne sont pas toujours bonnes.)

Alors une instrumentation typique nécessiterait l'introduction de quelques variables intermédiaires et blocs de code:

debug("a="+a)
debug("b="+b)
val x = if(condition1(a,b)) {
          debug("rule1 hit")
          val production = production1(a,b)
          debug("rule1 result: "+production)
          Some(production)
        } else { 
          debug("rule1 doesn't hit")
          debug("c="+c)
          debug("d="+d)
          if(condition2(c,d)){
            debug("rule2 hit")
            Some(production2(a,b))
          } else
            None
        }

Example 2:

def f(list:List[Int]) = 
    list.
        map(_*2).
        flatMap(t =>
            (0 until t).
            map(_+1)
        )

L'instrumentation conduira à des variables intermédiaires:

def f(list:List[Int]) = {        
    val twiced = list.map(_*2)
    debug(s"twiced = $twiced")
    val result = twiced.flatMap(t => {
        val widened = (0 until t).map(_+1)            
        debug(s"widened = $widened")
        widened
    })
    debug(s"result = $result")
    result
}

Très moche, je suppose. Et cette instrumentation prend plus de place que le code lui-même. La raison principale, je pense, est que l'enregistreur est incompatible avec un style d'évaluation d'expression.

Existe-t-il un moyen de consigner les valeurs d'expression de manière plus concise?

0

1 Réponses

J'ai récemment trouvé une bonne façon de consigner la valeur d'une expression:

trait Logging {

    protected val loggerName = getClass.getName
    protected lazy val logger = LoggerFactory.getLogger(loggerName)

    implicit class RichExpressionForLogging[T](expr: T){
        def infoE (msg: T ⇒ String):T = {if (logger.isInfoEnabled ) logger.info (msg(expr)); expr}
        def traceE(msg: T ⇒ String):T = {if (logger.isTraceEnabled) logger.trace(msg(expr)); expr}
        def debugE(msg: T ⇒ String):T = {if (logger.isDebugEnabled) logger.debug(msg(expr)); expr}      
        def infoL (msg: String):T = {if (logger.isInfoEnabled ) logger.info (msg+expr); expr}
        def traceL(msg: String):T = {if (logger.isTraceEnabled) logger.trace(msg+expr); expr}
        def debugL(msg: String):T = {if (logger.isDebugEnabled) logger.debug(msg+expr); expr}       
    }

}

Voici comment il est utilisé:

Exemple 1 (règles):

val x = if(condition1(a.debugL("a="),b.debugL("b="))) 
      Some(production1(a,b).debugL("rule1="))
    else if(condition2(c,d))
      Some(production2(a,b).debugL("rule2="))
    else
      None

Exemple 2:

def f(list:List[Int]) = 
    list.
        map(_*2).
        debugE(s"res1="+_).
        flatMap(t => (0 until t).
            map(_+1).
            debugE(s"res2="+_)).
        debugE(s"res="+_)

Il peut également être utilisé partout dans les expressions:

if((a<0).debugE(s"a=$a<0="+_))

for{
    a <- f(list).debugE("f(list)="+_)
    b <- a.debugL("a=")
} yield b.debugL("b=")

Bien sûr, vous devriez mélanger le trait d'enregistrement à votre classe.

Ce type d'instrumentation ne cache pas la logique du code.

0
ajouté
Il permet d'avoir des valeurs d'expression enregistrées sans passe-partout. Et peut être intégré directement dans une expression.
ajouté l'auteur Arseniy Zhizhelev, source
Eh bien, j'aurais dû mettre plus d'expressions de la vie réelle. Les exemples d'expressions sont un peu artificiels. Le but est d'avoir un moyen d'introduire l'évaluation d'expression sans avoir à la réécrire avec des vals intermédiaires.
ajouté l'auteur Arseniy Zhizhelev, source
E - expression, L - étiquette. Je ne pouvais pas utiliser le debug simple car il est déjà réservé à la variante : Unit . Vous pouvez suggérer d'autres noms pour les noms de méthodes.
ajouté l'auteur Arseniy Zhizhelev, source
Votre solution est bonne, mais certainement pas agréable. Cela semble être une solution pour surmonter les mauvaises pratiques de codage. Je dirais que si vous enchaînez tant de méthodes ensemble et que vous avez besoin de pirater la journalisation pour réellement déboguer votre code, alors vous ne savez pas ce que fait réellement votre code . Bon et clair, le code Scala a des variables intermédiaires nommées justement pour aider les autres programmeurs (et vous) à comprendre ce que fait le code. S'il vous plaît regarder
ajouté l'auteur Akos Krivachy, source
@ArseniyZhizhelev Ok, je suppose que cela fait un peu plus de sens maintenant. BTW, qu'est-ce que le E et le L représentent à la fin des méthodes?
ajouté l'auteur Akos Krivachy, source