69 votes

La journalisation conditionnelle avec un minimum de complexité cyclomatique

Après la lecture de "Quel est votre/une bonne limite pour la complexité cyclomatique?", Je me rends compte que beaucoup de mes collègues ont été très ennuyé avec cette nouvelle QA de la politique sur notre projet: les 10 plus de complexité cyclomatique par fonction.

Sens: pas plus de 10 'si', 'else', 'essayer', "attraper" et d'autres workflow code de la ramification de la déclaration. La droite. Comme je l'ai expliqué dans"avez-vous d'essai méthode privée?', une telle politique présente de nombreux effets secondaires.

Mais: Au début de notre (200 personnes - 7 ans) projet, nous avons été heureux d'enregistrement (et non, on ne peut pas facilement déléguer à une sorte de"programmation orientée Aspectsapproche pour les journaux).

myLogger.info("A String");
myLogger.fine("A more complicated String");
...

Et quand les premières versions de notre Système, nous avons connu d'énorme problème de mémoire non pas à cause de l'exploitation forestière qui était à un point éteint), mais parce que le journal des paramètres (les cordes), qui sont toujours calculés, puis transmise à la 'info()' ou 'amende()' fonctions, seulement pour découvrir que le niveau de journalisation est "OFF", et qu'aucun enregistrement n'étaient en train de prendre place!

Donc QA est revenu et a exhorté nos programmeurs de faire conditionnelle de l'exploitation forestière. Toujours.

if(myLogger.isLoggable(Level.INFO) { myLogger.info("A String");
if(myLogger.isLoggable(Level.FINE) { myLogger.fine("A more complicated String");
...

Mais maintenant, avec qui "peut-pas-être-déplacer' 10 complexité cyclomatique niveau par la fonction de limite, ils soutiennent que les différents journaux qu'ils mettent dans leur fonction est ressenti comme un fardeau, parce que chaque "si(isLoggable())" est compté comme +1 complexité cyclomatique!

Donc, si une fonction a 8 'si', 'autre' et ainsi de suite, dans un étroitement couplés pas-facilement partageable algorithme, et 3 critique du journal des actions... ils ont manqué à la limite, même si le conditionnel journaux peut-être pas vraiment de la partie de ladite complexité de cette fonction...

Comment réagiriez-vous face à cette situation ?
J'ai vu un couple d'intéressant codage de l'évolution (en raison de ce "conflit") dans mon projet, mais je veux juste obtenir vos pensées.


Merci pour toutes les réponses.
Je dois insister pour que le problème n'est pas "mise en forme" liées, mais l'argument de l'évaluation " (évaluation qui peut être très coûteux à faire, juste avant d'appeler une méthode qui permettra de ne rien faire)
Ainsi, lorsqu'un écrit ci-dessus "Une Chaîne", j'ai fait aFunction(), avec aFunction() retourne une Chaîne, et un appel à une méthode compliquée de la collecte et de l'informatique à tous les types de données de journal affiché par le logger... ou pas (d'où la question, et l' obligation d'utiliser la journalisation conditionnelle, d'où le problème réel de l'augmentation artificielle de la "complexité cyclomatique'...)

Maintenant, je reçois le"variadic fonction " point avancé par certains d'entre vous (merci John).
Remarque: un test rapide dans java6 montre que mon varargs fonction évalue ses arguments avant d'être appelé, de sorte qu'il ne peut pas être appliquée pour l'appel de fonction, mais pour "Journal retriever objet' (ou 'la fonction wrapper'), sur lequel le toString() sera appelée uniquement si nécessaire. L'a obtenu.

Maintenant j'ai posté mon expérience sur ce sujet.
Je vais l'y laisser jusqu'à mardi prochain pour le vote, puis je sélectionne l'un de vos réponses.
Encore une fois, merci pour toutes les suggestions :)

64voto

erickson Points 127945

Sont garde consolidés vraiment en ajoutant de la complexité?

Pensez à l'exclusion de la journalisation des gardes consolidés à partir de la complexité cyclomatique de calcul.

On pourrait faire valoir que, en raison de leur façon prévisible, à condition de journalisation vérifie vraiment de ne pas contribuer à la complexité du code.

Inflexible métriques peut faire un autre bon programmeur de mal tourner. Attention!

En supposant que vos outils de calcul de complexité ne peut pas être adapté à ce degré, l'approche suivante peut offrir une solution de rechange.

La nécessité pour la journalisation conditionnelle

Je suppose que vos gardes, des déclarations ont été introduites parce que vous avez eu un code comme ceci:

private static final Logger log = Logger.getLogger(MyClass.class);

Connection connect(Widget w, Dongle d, Dongle alt) 
  throws ConnectionException
{
  log.debug("Attempting connection of dongle " + d + " to widget " + w);
  Connection c;
  try {
    c = w.connect(d);
  } catch(ConnectionException ex) {
    log.warn("Connection failed; attempting alternate dongle " + d, ex);
    c = w.connect(alt);
  }
  log.debug("Connection succeeded: " + c);
  return c;
}

En Java, chaque journal déclarations crée un nouveau StringBuilder, et appelle l' toString() méthode sur chaque objet concaténée à la chaîne. Ces toString() méthodes, à leur tour, sont susceptibles de créer StringBuilder cas de leur propre, et d'invoquer l' toString() méthodes de leurs membres, et ainsi de suite, à travers un large objet graphique. (Avant Java 5, il était encore plus chère, puisqu' StringBuffer a été utilisé, et l'ensemble de ses opérations sont synchronisés.)

Ceci peut être assez coûteux, surtout si l'instruction de journal est dans certains fortement le code exécuté chemin. Et, à l'écrit comme ci-dessus, que cher mise en forme du message se produit même si l'enregistreur est lié à jeter le résultat, parce que le niveau de journalisation est trop élevé.

Cela conduit à l'introduction de la garde des énoncés de la forme:

  if (log.isDebugEnabled())
    log.debug("Attempting connection of dongle " + d + " to widget " + w);

Avec cette garde, l'évaluation des arguments d et w et la concaténation de chaîne est effectuée uniquement lorsque nécessaire.

Une solution simple, efficace journalisation

Toutefois, si l'enregistreur de données (ou un wrapper de ce que vous écrivez autour de votre choisi la journalisation package) prend un formateur et les arguments pour le formateur, le message de la construction peut être retardée jusqu'à ce qu'il est certain qu'il va être utilisé, tout en éliminant la garde des déclarations et de leur complexité cyclomatique.

public final class FormatLogger
{

  private final Logger log;

  public FormatLogger(Logger log)
  {
    this.log = log;
  }

  public void debug(String formatter, Object... args)
  {
    log(Level.DEBUG, formatter, args);
  }

  … &c. for info, warn; also add overloads to log an exception …

  public void log(Level level, String formatter, Object... args)
  {
    if (log.isEnabled(level)) {
      /* 
       * Only now is the message constructed, and each "arg"
       * evaluated by having its toString() method invoked.
       */
      log.log(level, String.format(formatter, args));
    }
  }

}

class MyClass 
{

  private static final FormatLogger log = 
     new FormatLogger(Logger.getLogger(MyClass.class));

  Connection connect(Widget w, Dongle d, Dongle alt) 
    throws ConnectionException
  {
    log.debug("Attempting connection of dongle %s to widget %s.", d, w);
    Connection c;
    try {
      c = w.connect(d);
    } catch(ConnectionException ex) {
      log.warn("Connection failed; attempting alternate dongle %s.", d);
      c = w.connect(alt);
    }
    log.debug("Connection succeeded: %s", c);
    return c;
  }

}

Maintenant, aucun de la cascade toString() des appels avec leur tampon allocations de se produire , sauf s'ils sont nécessaires! Cela élimine efficacement les performances qui ont conduit à la garde des déclarations. Une petite pénalité, en Java, ce serait de l'auto-boxing de n'importe quel type primitif arguments que vous avez passer à l'enregistreur de données.

Le code fait l'enregistrement est sans doute encore plus propre que jamais, depuis désordre de concaténation de chaîne est allé. Il peut être encore plus propre si les chaînes de format sont externalisés (à l'aide d'un ResourceBundle), ce qui pourrait également contribuer à l'entretien ou à la localisation du logiciel.

D'autres améliorations

À noter également que, en Java, une MessageFormat de l'objet peut être utilisé à la place d'un "format" String, ce qui vous donne des fonctionnalités supplémentaires telles que un choix de format pour manipuler des nombres cardinaux plus proprement. Une autre alternative serait de mettre en place votre propre mise en forme de capacités qui invoque une interface qui vous définissent, pour "l'évaluation", plutôt que sur la base toString() méthode.

33voto

John Millikin Points 86775

En Python, vous passer à la mise en forme des valeurs comme paramètres de la fonction de journalisation. Chaîne de mise en forme est appliquée uniquement si la journalisation est activée. Il y a encore la charge d'un appel de fonction, mais c'est infime par rapport à la mise en forme.

log.info ("a = %s, b = %s", a, b)

Vous pouvez faire quelque chose comme cela pour n'importe quelle langue avec variadic arguments (C/C++, C#/Java, etc).


Ce n'est pas vraiment prévu pour quand les arguments sont difficiles à récupérer, mais quand leur formatage de chaînes de caractères est cher. Par exemple, si votre code a déjà une liste de nombres, vous pouvez consigner cette liste à des fins de débogage. L'exécution d' mylist.toString() va prendre un certain temps pour aucun avantage, que le résultat sera jeté. Si vous passez mylist en tant que paramètre à la fonction de journalisation, et laissez-poignée de chaîne de formatage. De cette façon, la mise en forme ne sera effectué si nécessaire.


Depuis l'OP question mentionne spécifiquement Java, voici comment le ci-dessus peut être utilisé:

Je dois insister pour que le problème n'est pas "mise en forme" liées, mais l'argument de l'évaluation " (évaluation qui peut être très coûteux à faire, juste avant d'appeler une méthode qui permettra de ne rien faire)

Le truc est d'avoir des objets qui n'effectuent pas cher des calculs jusqu'absolument nécessaire. C'est facile dans des langages comme Smalltalk ou Python qui prennent en charge les lambdas et de fermetures, mais reste faisable en Java avec un peu d'imagination.

Dire que vous avez une fonction get_everything(). Il permettra de récupérer tous les objets de votre base de données dans une liste. Vous ne voulez pas appeler cette fonction si le résultat va être mis au rebut, évidemment. Donc, au lieu d'utiliser un appel à cette fonction directement, vous définir une sous-classe appelée LazyGetEverything:

public class MainClass {
    private class LazyGetEverything { 
        @Override
        public String toString() { 
            return getEverything().toString(); 
        }
    }

    private Object getEverything() {
        /* returns what you want to .toString() in the inner class */
    }

    public void logEverything() {
        log.info(new LazyGetEverything());
    }
}

Dans ce code, l'appel à l' getEverything() est enveloppé de sorte qu'il ne sera pas effectivement être exécutée jusqu'à ce qu'il est nécessaire. La fonction de journalisation exécutera toString() sur ses paramètres uniquement si le débogage est activé. De cette façon, votre code ne souffrent que de la charge d'un appel de fonction au lieu de l'intégralité getEverything() appel.

6voto

pointernil Points 446

Dans les langues soutenir les expressions lambda ou des blocs de code en tant que paramètres, une solution serait de donner juste que, pour la méthode de journalisation. Que l'on peut évaluer la configuration et seulement en cas de besoin fait appel/exécuter la condition lambda/bloc de code. Ne pas essayer encore, cependant.

Théoriquement c'est possible. Je ne voudrais pas l'utiliser dans la production due à des problèmes de performances-je m'attendre avec une utilisation intensive de lamdas/blocs de code pour l'enregistrement.

Mais comme toujours: en cas de doute, tester et mesurer l'impact sur la charge du processeur et de la mémoire.

4voto

flipdoubt Points 4140

C'est peut-être trop simple, mais qu'en utilisant la "méthode d'extraction" refactoring autour de la garde de la clause? Votre exemple de code:

public void Example()
{
  if(myLogger.isLoggable(Level.INFO))
      myLogger.info("A String");
  if(myLogger.isLoggable(Level.FINE))
      myLogger.fine("A more complicated String");
  // +1 for each test and log message
}

Devient:

public void Example()
{
   _LogInfo();
   _LogFine();
   // +0 for each test and log message
}

private void _LogInfo()
{
   if(!myLogger.isLoggable(Level.INFO))
      return;

   // Do your complex argument calculations/evaluations only when needed.
}

private void _LogFine(){ /* Ditto ... */ }

3voto

Tom Ritter Points 44352

En C ou C++ je voudrais utiliser le préprocesseur au lieu de le si des instructions pour le conditionnel de l'exploitation forestière.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X