web-dev-qa-db-fra.com

Logement conditionnel avec une complexité cyclomatique minimale

Après avoir lu " Quelle est votre/une bonne limite pour la complexité cyclomatique? ", je me rends compte que beaucoup de mes collègues ont été très agacé par ce nouveau QA politique sur notre projet: plus de 10 complexité cyclomatique par fonction.

Signification: pas plus de 10 " si ", " autre ", " essayer ", " attraper " et d'autres flux de travail de branchement déclaration code. Droit. Comme je l'ai expliqué dans " Avez-vous la méthode d'essai privée? ", une telle politique a beaucoup de bons effets secondaires.

Mais: Au début de nos (200 personnes - 7 ans) approche projet, nous enregistrons heureusement (et non, nous ne pouvons pas facilement déléguer cela à une sorte de " programmation orientée aspect " pour journaux).

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

Et lorsque les premières versions de notre problème de mémoire système a en direct, nous avons connu énorme pas à cause de l'exploitation forestière (qui était à un moment donné éteint), mais à cause des paramètres du journal ( les cordes), qui sont toujours calculés, puis transmis à la " info () " ou 'bien () fonctions, seulement pour découvrir que le niveau de l'exploitation forestière était " OFF ", et qu'aucune exploitation forestière ont lieu!

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

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

Mais maintenant, avec ce " peut-pas-déplacer " 10 niveau de complexité cyclomatique limite par fonction, ils font valoir que les différents journaux qu'ils mettent dans leur fonction est ressentie comme un fardeau, parce que chaque " si (isLoggable ()) " est compté comme une complexité cyclomatique!

Donc, si une fonction a 8 " si ", " autre " et ainsi de suite, dans un bien couplé algorithme non facilement partageable, et 3 actions journaux critiques ... ils ne respectent pas la limite même si les journaux conditionnels peuvent ne pas être vraiment une partie de ladite complexité de cette fonction ...

Comment aborderez-vous cette situation?
Je l'ai vu un couple d'évolution intéressante de codage (en raison de ce " conflit ") dans mon projet, mais je veux juste obtenir vos pensées d'abord.


Merci pour toutes les réponses.
Je dois insister sur le fait que le problème ne soit pas " formatage " lié, mais " l'évaluation de l'argument " lié (évaluation qui peut être très coûteuse à faire, juste avant d'appeler une méthode qui ne fera rien)
Alors, quand un écrit ci-dessus " Une chaîne ", je voulais dire AFunction (), avec AFunction () retourne une chaîne, et d'être un appel à une méthode compliquée de collecte et de calcul tous les types de données du journal à afficher par l'enregistreur ... ou non (d'où la question, et obligation à utiliser la journalisation conditionnelle, d'où la question réelle de l'augmentation artificielle de la " complexité cyclomatique " ...)

Je reçois maintenant " variadique fonction " point avancé par certains d'entre vous (merci John).
[. .____] Note: un test rapide en montre java6 que mon varargs fonction n'évalue ses arguments avant d'être appelé, il ne peut pas être appliquée pour les appels de fonction, mais pour " Log objet chien d'arrêt " ( ou " emballage de fonction "), sur laquelle le toString () ne sera appelé en cas de besoin. J'ai compris.

Je suis maintenant posté mon expérience sur ce sujet.
Je laisse là jusqu'à mardi prochain pour le vote, alors je vais choisir une de vos réponses.
Encore une fois, merci pour toutes les suggestions :)

64
VonC

In Python Vous transmettez les valeurs formatées en tant que paramètres à la fonction de journalisation. La mise en forme de chaîne n'est appliquée que si la journalisation est activée. Il y a toujours la surcharge d'un appel de la fonction, mais c'est minuscule par rapport au formatage.

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

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


Cela n'est pas vraiment destiné au moment où les arguments sont difficiles à récupérer, mais pour le formater les formatage des chaînes est coûteux. Par exemple, si votre code a déjà une liste de chiffres, vous souhaiterez peut-être enregistrer cette liste pour le débogage. Exécution mylist.toString() prendra un certain temps à aucun avantage, car le résultat sera jeté. Donc, vous passez mylist comme paramètre à la fonction de journalisation et laissez-la en train de gérer la mise en forme de chaîne. De cette façon, le formatage ne sera effectué que si nécessaire.


Depuis que la question de l'OP mentionne spécifiquement Java, voici comment ce qui précède peut être utilisé:

Je dois insister sur le fait que le problème n'est pas "formatage" associé, mais "évaluation des arguments" (évaluation qui peut être très coûteuse à faire, juste avant d'appeler une méthode qui ne fera rien)

L'astuce consiste à avoir des objets qui n'effectueront pas de calculs coûteux jusqu'à ce qu'elles soient absolument nécessaires. Ceci est facile dans des langues comme SmallTalk ou Python qui prennent en charge les lambdas et les fermetures, mais est toujours faisable en Java avec un peu d'imagination.

Dites que vous avez une fonction get_everything(). Il récupérera chaque objet de votre base de données dans une liste. Vous ne voulez pas appeler cela si le résultat sera jeté, évidemment. Donc, au lieu d'utiliser directement un appel à cette fonction, vous définissez une classe interne 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 à getEverything() est enveloppé de manière à ce qu'elle ne soit réellement exécutée tant qu'elle n'est pas 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 souffrira que de la surcharge d'un appel de la fonction au lieu de l'appel complet getEverything() appel.

31
John Millikin

Dans les langues prenant en charge les expressions Lambda ou les blocs de code en tant que paramètres, une solution pour cela serait de donner simplement à la méthode de la journalisation. Celui-ci pourrait évaluer la configuration et uniquement si nécessaire, appelez/exécutez le bloc Lambda/Code fourni. N'a pas encore essayé, cependant.

théoriquement C'est possible. Je ne voudrais pas l'utiliser dans la production en raison de problèmes de performance que j'attends avec cette forte utilisation de blocs Lamdas/Code pour la journalisation.

Mais comme toujours: En cas de doute, testez-le et mesurez l'impact sur la charge et la mémoire de la CPU.

6
pointernil

Peut-être que c'est trop simple, mais qu'envoyez l'utilisation de la "méthode d'extrait" refactoring autour de la clause de garde? Votre exemple de code de ceci:

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 ceci:

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 ... */ }
4
flipdoubt

Merci pour toutes vos réponses! Vous êtes top les gars :)

Maintenant, mes commentaires ne sont pas aussi directs que le vôtre:

Oui, pour un projet (comme dans "un programme déployé et exécuté seul sur une seule plate-forme de production"), je suppose que vous pouvez utiliser toute technique sur moi:

  • objets dédiés à "God Retriever", qui peuvent être passés à un wrapper de l'enregistreur uniquement appelant Tostring () est nécessaire
  • utilisé conjointement avec une journalisation fonction variadique (ou un tableau d'objet uni []!)

et là, vous l'avez, comme expliqué par @john Millikin et @erickson.

Cependant, cette question nous a forcé à réfléchir un peu sur "pourquoi exactement nous nous sommes connectés en premier lieu?"
[.____] Notre projet est en fait de 30 projets différents (5 à 10 personnes) déployés sur diverses plateformes de production, avec des besoins de communication asynchrones et une architecture centrale de bus.
[.____] La simple journalisation décrite dans la question était bonne pour chaque projet au début (il y a 5 ans), mais depuis lors, nous devons intensifier. Entrez le KPI .

Au lieu de demander à un enregistreur pour vous connecter quelque chose, nous demandons à un objet créé automatiquement (appelé KPI) pour enregistrer un événement. C'est un appel simple (mykpi.i_am_signaling_myself_to_you ()) et n'a pas besoin d'être conditionnel (qui résout la "augmentation artificielle de la complexité cyclomatique").

Cet objet KPI sait qui l'appelle et puisqu'il court depuis le début de la demande, il est capable de récupérer de nombreuses données que nous étions auparavant informatiques sur place lorsque nous nous enregistrons.
[.____] Plus cet objet KPI peut être surveillé indépendamment et calculer/publier sur demande ses informations sur un bus de publication unique et séparé.
De cette façon, chaque client peut demander les informations qu'il souhaite réellement (comme, mon processus a commencé, et si oui, depuis quand? Chaîne de caractères...

En effet, la question "Pourquoi exactement nous nous sommes connectés en premier lieu?" Nous avons fait comprendre que nous n'avions pas connecté uniquement au programmeur et à ses tests d'unité ou d'intégration, mais pour une communauté beaucoup plus large, y compris certains des clients finaux eux-mêmes. Notre mécanisme "rapport" a dû être centralisé, asynchrone, 24/7.

Le mécanisme de KPI spécifique est hors de la portée de cette question. Il suffit de dire que son étalonnage approprié est de loin, les mains vers le bas, la seule question la plus compliquée non fonctionnelle auxquelles nous sommes confrontés. Il apporte toujours le système sur son genou de temps en temps! Bien calibré cependant, c'est un économiseur de vie.

Encore une fois, merci pour toutes les suggestions. Nous les examinerons pour certaines parties de notre système lorsque la simple journalisation est toujours en place.
[.____] Mais l'autre point de cette question était de vous illustrer un problème spécifique dans un contexte beaucoup plus large et plus compliqué.
J'espère que tu l'as aimé. Je pourrais poser une question sur KPI (qui, croire ou non, n'est dans aucune question sur SOF jusqu'à présent!) Plus tard la semaine prochaine.

Je laisserai cette réponse pour voter jusqu'au mardi prochain, puis je choisirai une réponse (pas celle-ci évidemment;))

4
VonC

Passez le niveau de journal à l'enregistreur et laissez-la décider de l'inscription de journal:

//if(myLogger.isLoggable(Level.INFO) {myLogger.info("A String");
myLogger.info(Level.INFO,"A String");

Mise à jour: AH, je vois que vous souhaitez créer de manière conditionnelle la chaîne de journal sans une déclaration conditionnelle. Probablement au moment de l'exécution plutôt que de compiler le temps.

Je vais simplement dire que la façon dont nous avons résolu ceci est de mettre le code de formatage dans la classe d'enregistreurs afin que le formatage ne se déroule que si le niveau passe. Très similaire à un sprintf intégré. Par exemple:

myLogger.info(Level.INFO,"A String %d",some_number);   

Cela devrait répondre à vos critères.

3
Greg Miskin

La journalisation conditionnelle est diabolique. Il ajoute un fouillis inutile à votre code.

Vous devez toujours envoyer les objets que vous avez à l'enregistreur:

Logger logger = ...
logger.log(Level.DEBUG,"The foo is {0} and the bar is {1}",new Object[]{foo, bar});

puis avoir un java.util.logging.formatter qui utilise MessageFormat pour aplatir la FOO et la barre dans la chaîne pour être sortie. Il ne sera appelé que si l'enregistreur et le gestionnaire se connecteront à ce niveau.

Pour plus de plaisir, vous pourriez avoir une sorte de langage d'expression pour pouvoir obtenir un contrôle précis sur la manière de formater les objets enregistrés (la totring peut ne pas toujours être utile).

2
simon

alt text
(Source: scala-lang.org )

Scala a une annontation @ Elidable () qui vous permet de supprimer des méthodes avec un drapeau du compilateur.

Avec le scala repl:

C:> Scala

Bienvenue sur Scala Version 2.8.0.Finale (Java Hotspot (TM) Serveur 64-bit VM, Java 1. 6.0_16). Tapez les expressions à demandez-leur d'être évalué. Tapez: aide pour plus d'informations.

scala> Importer Scala.Annotation.LIDEABLE Import Scala.Annotation.LIDEBLABLE

scala> importer scala.annotation.elidable._ Importer Scala.Annotation.eLidable._

sCALA> @ELIDABLE (FINE) DEL LOGDEBUG (arg: string) = println (arg)

logdebug: (arg: chaîne) unité

sCALA> LOGDEBUG ("Test")

scala>

Avec Elide-Beloset

C:> SCALA -XELIDE-CI-DESSOUS 0

Bienvenue sur Scala Version 2.8.0.Finale (Java Hotspot (TM) Serveur 64-bit VM, Java 1. 6.0_16). Tapez les expressions à demandez-leur d'être évalué. Tapez: aide pour plus d'informations.

scala> Importer Scala.Annotation.LIDEABLE Import Scala.Annotation.LIDEBLABLE

scala> importer scala.annotation.elidable._ Importer Scala.Annotation.eLidable._

sCALA> @ELIDABLE (FINE) DEL LOGDEBUG (arg: string) = println (arg)

logdebug: (arg: chaîne) unité

sCALA> LOGDEBUG ("Test")

essai

scala>

Voir aussi Scala Assert Définition

2
oluies

Considérons une fonction d'emploi de journalisation ...

void debugUtil(String s, Object… args) {
   if (LOG.isDebugEnabled())
       LOG.debug(s, args);
   }
);

Ensuite, faites appel à une "fermeture" autour de l'évaluation coûteuse que vous souhaitez éviter.

debugUtil(“We got a %s”, new Object() {
       @Override String toString() { 
       // only evaluated if the debug statement is executed
           return expensiveCallToGetSomeValue().toString;
       }
    }
);
1
johnlon

Voici une solution élégante utilisant l'expression ternaire

logger.info (logger.isinfoenabled ()? "Déclaration de journal va ici ...": null);

1
Muhammad Atif Riaz

Autant que je déteste les macros en C/C++, au travail que nous avons #defines pour la partie IF, que si faux ignore (n'évalue pas) les expressions suivantes, mais si vrai renvoie un flux dans lequel des trucs peuvent être canalisés à l'aide de " << 'opérateur. Comme ça:

LOGGER(LEVEL_INFO) << "A String";

Je suppose que cela éliminerait la "complexité" supplémentaire que votre outil voit et élimine également tout calcul de la chaîne ou toutes les expressions à enregistrer si le niveau n'était pas atteint.

1
quamrana