Frage

Nach der Lektüre „ Was ist Ihr / eine gute Grenze für zyklomatische Komplexität? “, merke ich viele meiner Kollegen mit dieser neuen QA Politik auf unser Projekt ziemlich verärgert waren . nicht mehr 10 zyklomatische Komplexität pro Funktion

Bedeutung: nicht mehr als 10 ‚wenn‘, ‚sonst‘, ‚versuchen‘, ‚Fang‘ und anderen Code Workflow Verzweigungs Anweisung. Richtig. Wie ich in erklärt ‚ Haben Sie private Methode testen? ‘, eine solche Politik viele gute Nebenwirkungen hat.

Aber: Zu Beginn unserer (200 Personen - 7 Jahre) Projekt, wir waren glücklich Protokollierung (und nein, wir können nicht einfach übertragen, der zu einer Art ‚ Aspektorientierte orientierte~~POS=HEADCOMP Programmierung ‘ -Ansatz für Protokolle).

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

Und wenn die ersten Versionen unseres System Betrieb genommen wurden, erleben wir großes Speicherproblem nicht wegen der Protokollierung (das war an einem Punkt ausgeschaltet), sondern wegen der Logparameter (der Saiten ), die immer berechnet wird, dann zur bestanden ‚info ()‘ oder ‚fein ()‘ Funktionen, nur dass das Niveau der Protokollierung ‚aUS‘ war zu entdecken, und dass keine Protokollierung stattfänden!

So QA kam zurück und forderte unsere Programmierer bedingte Protokollierung zu tun. Immer.

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

Aber jetzt, mit, dass ‚can-nicht-sein-bewegt‘ 10 zyklomatischen Komplexitätsgrad pro Funktion Grenze, so argumentieren sie, dass die verschiedenen Protokolle sie in ihrer Funktion gesetzt wird als Last empfand, weil jeder „if (isLoggable () )“als +1 zyklomatische Komplexität gezählt!

Also, wenn eine Funktion hat 8 ‚wenn‘, ‚sonst‘ und so weiter, in einer eng gekoppelten nicht-leicht teilbarem Algorithmus und 3 kritische Protokoll Aktionen ... sie die Grenze durchbrechen, obwohl die bedingten Protokolle können sein nicht wirklich Teil der Komplexität dieser Funktion ...

Wie würden Sie diese Situation lösen?
Ich habe ein paar interessante Codierung Evolution gesehen (wegen dieses ‚Konflikt‘) in meinem Projekt, aber ich will nur ersten Gedanken bekommen.


Danke für alle Antworten.
Ich muss darauf bestehen, dass das Problem nicht ist ‚Formatierung‘ verwandt, aber ‚Argument Evaluation‘ im Zusammenhang (Bewertung, die sehr teuer sein kann zu tun, kurz vor dem Aufruf einer Methode, die nichts anderes tun wird)
Also, wenn oben „A String“ a schreibe, habe ich eigentlich gemeint aFunction (), mit aFunction () Zurückgeben einen String und einen Anruf zu einem komplizierten Verfahren Sammel zu sein und die Berechnung aller Art von Protokolldaten vom Logger angezeigt werden ... oder nicht (daher die Frage, und die Verpflichtung bedingte Protokollierung zu verwenden, damit die eigentliche Frage der künstlichen Erhöhung von ‚zyklomatische Komplexität‘ ...)

Ich bekomme jetzt den ‚ variadische Funktion‘ Punkt für einige von Ihnen vorgeschoben (danke John ).
Hinweis: ein Schnelltest in java6 zeigt, dass meine varargs Funktion hat seine Argumente zu bewerten, bevor aufgerufen wird, so kann es nicht für Funktionsaufruf angewendet werden, aber für ‚Log-retriever-Objekt‘ (oder ‚Funktion Wrapper‘), auf der die toString () wird nur bei Bedarf abgerufen werden. Verstanden.

ich habe nun meine Erfahrungen zu diesem Thema.
Ich werde es dort zur Abstimmung bis zum nächsten Dienstag verlassen, dann werde ich eine Ihrer Antworten auswählen.
Nochmals vielen Dank für alle Vorschläge:)

War es hilfreich?

Lösung

In Python übergeben Sie die formatierten Werte als Parameter an die Logging-Funktion. String-Formatierung wird nur angewendet, wenn die Protokollierung aktiviert ist. Es ist immer noch der Aufwand für einen Funktionsaufruf, aber das ist winzig im Vergleich zu Formatierung.

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

Sie können mit variadische Argumenten (C / C ++, C # / Java, usw.) für jede Sprache so etwas tun.


Dies ist nicht wirklich gedacht für, wenn die Argumente sind schwer zu holen, aber für, wenn sie in Strings Formatierung ist teuer. Zum Beispiel schon, wenn Ihr Code eine Liste von Zahlen in sich hat, können Sie diese Liste für das Debuggen protokollieren. Die Ausführung mylist.toString() wird eine Weile zu keinem Nutzen nehmen, da das Ergebnis weggeworfen werden wird. So passieren Sie mylist als Parameter an die Logging-Funktion, und lassen Sie es String-Formatierung handhaben. Auf diese Weise Formatierung wird nur bei Bedarf durchgeführt werden.


Da die Frage des OP speziell Java erwähnt, ist hier, wie die oben verwendet werden können:

  

Ich muß darauf bestehen, dass das Problem nicht ‚Formatierung‘ verwandt, aber ‚Argument Evaluation‘ im Zusammenhang (Bewertung, die sehr teuer sein kann zu tun, kurz vor dem Aufruf eine Methode, die nichts anderes tun wird)

Der Trick besteht darin, Objekte zu haben, die teure Berechnungen erst absolut erforderlich durchführen wird. Dies ist einfach in Sprachen wie Smalltalk oder Python, die Lambda-Ausdrücke und Verschlüsse unterstützen, ist aber immer noch machbar in Java mit einem wenig Phantasie.

Sagen Sie bitte eine Funktion get_everything() haben. Es wird jedes Objekt aus Ihrer Datenbank in eine Liste abrufen. Sie wollen nicht, diese zu nennen, wenn das Ergebnis verworfen werden, offensichtlich. Anstatt also einen Aufruf dieser Funktion direkt zu verwenden, definieren Sie eine innere Klasse namens 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());
    }
}

In diesem Code wird der Anruf an getEverything() so gewickelt, dass es nicht tatsächlich ausgeführt werden, bis es gebraucht wird. Die Logging-Funktion wird ausgeführt toString() auf seine Parameter nur, wenn das Debugging aktiviert ist. Auf diese Weise Ihr Code nur den Overhead eines Funktionsaufrufs anstelle des vollständigen getEverything() Ruf leiden.

Andere Tipps

Mit aktuellem Logging-Frameworks ist die Frage strittig

Aktuelles Logging Frameworks wie slf4j oder log4j 2 erfordert keine Wache Aussagen in den meisten Fällen. Sie verwenden eine Anweisung parametrisierte Protokoll, so dass ein Ereignis unbedingt angemeldet werden kann, aber Nachrichtenformatierung erfolgt nur, wenn das Ereignis aktiviert ist. Nachricht Konstruktion wird, wie durch den Logger benötigt durchgeführt, anstatt präventiv durch die Anwendung.

Wenn Sie eine antike Logging-Bibliothek verwenden, können Sie weitere Hintergrundinformationen zu erhalten und eine Art und Weise lesen, um die alte Bibliothek mit parametrisierte Meldungen nachzurüsten.

Sind guard Aussagen wirklich Komplexität hinzufügen?

Betrachten Wache Logging-Anweisungen von der zyklomatische Komplexität Berechnung ausgenommen.

Es könnte argumentiert, dass aufgrund ihrer vorhersagbaren Form, bedingte Protokollierung Kontrollen wirklich nicht dazu beitragen, die Komplexität des Codes.

Inflexible Metriken kann ein sonst guter Programmierer drehen schlecht machen. Seien Sie vorsichtig!

Unter der Annahme, dass Ihre Werkzeuge für die Komplexität der Berechnung nicht zu diesem Grad angepasst werden, kann die folgende Vorgehensweise bietet eine Behelfslösung.

Die Notwendigkeit für die bedingte Protokollierung

Ich gehe davon aus, dass der Hut Aussagen eingeführt wurden, weil Sie Code wie folgt hatte:

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;
}

In Java, jeder der Protokollanweisungen erstellt eine neue StringBuilder, und ruft die toString() Methode für jedes Objekt in die Zeichenfolge verkettet. Diese toString() Methoden, die wiederum wahrscheinlich StringBuilder Instanzen ihrer eigenen, und rufen Sie die toString() Methoden ihrer Mitglieder, und so weiter, über eine potenziell große Objektdiagramm erstellen. (Vor Java 5, es war sogar noch teurer, da StringBuffer verwendet wurde, und alle ihre Operationen synchronisiert werden.)

Dies kann relativ teuer sein, vor allem, wenn die Log-Anweisung in einigen stark ausgeführte Codepfad ist. Und geschrieben, wie oben, dass teure Nachrichtenformatierung tritt auf, selbst wenn der Logger gebunden ist, um das Ergebnis zu verwerfen, weil die Protokollebene zu hoch ist.

Dies führt zur Einführung von Schutzerklärung der Form:

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

Mit dieser Wache, die Auswertung der Argumente d und w und der String-Verkettung wird nur durchgeführt, wenn notwendig.

Eine Lösung für einfache, effiziente Protokollierung

Wenn jedoch der Logger (oder ein Wrapper, die Sie um Ihr gewähltes Logging-Paket schreiben) einen Formatierer und Argumente für die Formatierer nimmt, kann die Nachricht Bau verzögert werden, bis sicher ist, dass es verwendet wird, während die Wachen beseitigen Aussagen und deren zyklomatische Komplexität.

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;
  }

}

Jetzt keine der Kaskadierung toString() ruft mit ihren Pufferzuweisungen auftreten , wenn sie notwendig sind! Dies eliminiert effektiv die Leistung getroffen, die auf die Wache Aussagen geführt. Eine kleine Strafe, in Java, wäre Auto-Boxen aller Urtyp Argumente, die Sie an den Logger übergeben.

Der Code, um die Protokollierung zu tun ist wohl noch sauberer als je zuvor, da unordentliche String-Verkettung ist weg. Es kann sogar sauberer sein, wenn die Formatstrings (unter Verwendung eines ResourceBundle) externalisiert werden, die auch bei der Wartung oder Lokalisierung der Software helfen könnten.

Weitere Verbesserungen

Beachten Sie auch, dass in Java, ein MessageFormat Objekt anstelle einem „Format“ String verwendet werden könnte, die Ihnen zusätzliche Funktionen wie eine Wahl Format gibt mehr ordentlich Kardinalzahl zu handhaben. Eine andere Alternative wäre eine eigene Formatierung Fähigkeit zu implementieren, die eine Schnittstelle aufruft, die Sie für „Auswertung“ definieren, anstatt die Grund toString() Methode.

In Sprachen unterstützen Lambda-Ausdrücke oder Codeblocks als Parameter, eine Lösung hierfür wäre, genau das zu dem Protokollierungsmethode zu geben. Dass man könnte die Konfiguration bewerten und nur dann, wenn tatsächlich rufen / execute den mitgelieferte Lambda / Codeblock benötigt. obwohl Hat es noch nicht versuchen,.

Theoretisch ist dies möglich. Ich würde nicht wegen Performance-Probleme in der Produktion wie es zu benutzen i mit dem starken Gebrauch von lamdas / Codeblöcken für die Protokollierung erwartet.

Aber wie immer. Wenn Sie Zweifel haben, es testen und die Auswirkungen auf die CPU-Last und Speicher messen

Vielen Dank für Ihre Antworten! You guys rock:)

Jetzt ist mein Feedback ist nicht so geradlinig wie bei Ihnen:

Ja, für ein Projekt (wie in ‚ein Programm bereitgestellt und auf einer einzigen Produktionsplattform auf eigene laufen‘), nehme ich an Sie alle technischen mir gehen:

  • dedicated 'Log Retriever' Objekte, die nur zu einem Logger Wrapper passieren kann toString () aufrufen, ist notwendig,
  • in Verbindung mit einer Protokollierung variadische Funktion (oder ein einfaches Object [] Array! )

und dort haben Sie es, wie erläutert von @John Millikin und @erickson.

Allerdings gezwungen dieses Problem uns ein wenig zu denken: ‚Warum genau wir in erster Linie wurden die Anmeldung?‘
Unser Projekt ist tatsächlich 30 verschiedene Projekte (5 bis 10 Personen jeweils) zum Einsatz auf verschiedene Produktionsplattformen, mit asynchronen Kommunikationsanforderungen und die zentralen Bus-Architektur.
Die einfache Protokollierung in der Frage beschrieben war in Ordnung für jedes Projekt am Anfang (Vor 5 Jahren), aber seitdem hat man bis zu treten. Geben Sie die KPI .

Statt zu einem Logger zu fragen, alles zu protokollieren, bitten wir um ein automatisch erstellte Objekt (genannt KPI) ein Ereignis zu registrieren. Es ist ein einfacher Anruf (myKPI.I_am_signaling_myself_to_you ()), und muss nicht abhängig sein (was die ‚künstliche Erhöhung der zyklomatische Komplexität‘ Problem löst).

Die KPI-Objekt weiß, wer es nennt, und da er von Anfang an der Anwendung ausgeführt wird, ist er in der Lage viele Daten abgerufen werden wir zuvor wurden an Ort und Stelle der Berechnung als wir anzumelden.
Plus, dass KPI-Objekt kann unabhängig überwacht werden und berechnen / veröffentlicht auf Wunsch ihre Informationen auf einem einzigen und separaten Publikation Bus.
Auf diese Weise kann jeder Kunde die Daten abfragen will er tatsächlich (wie, ‚hat meinen Prozess begonnen, und wenn ja, seit wann?‘), Anstatt für die richtige Protokolldatei suchen und greppen für eine kryptische String ...

Tatsächlich ist die Frage: ‚Warum genau wurden Protokollierung wir in erster Linie?‘ nur für den Programmierer und seine Einheit oder Integrationstests, aber für eine viel breitere Gemeinschaft machte uns klar, wir waren einige der Endkunden einschließlich mich nicht anzumelden. Unser 'Reporting' -Mechanismus zentralisiert werden mußte, asynchron, 24/7.

Die spezifische dieser KPI Mechanismus ist Art und Weise aus dem Rahmen dieser Frage. Es genügt zu sagen, die richtige Kalibrierung ist bei weitem, die Hände nach unten, das einzelnen komplizierteste nicht-funktionale Problem, das wir konfrontiert sind. Es ist immer noch bringt das System auf die Knie von Zeit zu Zeit! Richtig jedoch kalibriert, es ist ein Lebensretter.

Nochmals vielen Dank für alle Vorschläge. Wir werden sie für einige Teile unseres Systems berücksichtigen, wenn einfache Protokollierung noch an seinem Platz ist.
Aber der andere Punkt dieser Frage wurde Ihnen ein bestimmtes Problem in einem viel größeren und komplizierteren Kontext darzustellen.
Hoffe, dass es Ihnen gefallen hat. Ich könnte eine Frage auf KPI fragen (was, glaube oder nicht, ist nicht in jeder Frage auf SOF so weit!) Später nächste Woche.

Ich werde diese Antwort überlasse zur Abstimmung bis zum nächsten Dienstag, dann werde ich eine Antwort wählen (nicht diesen offensichtlich;))

Vielleicht ist das zu einfach, aber was ist das „Extrakt-Methode“ Refactoring um die Wache-Klausel? Ihr Beispiel-Code dafür:

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
}

Wird dies:

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

In C oder C ++ ich den Präprozessor für die bedingte Protokollierung statt der if-Anweisungen verwenden würde.

Führen Sie die Protokollebene an den Logger und lassen Sie sich entscheiden, ob die Log-Anweisung zu schreiben:

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

UPDATE: Ah, ich sehe, dass Sie die Log-String ohne eine bedingte Anweisung bedingt erstellen möchten. Vermutlich zur Laufzeit statt Zeit kompilieren.

Ich werde einfach sagen, dass die Art, wie wir dieses Problem gelöst haben, ist die Formatierung Code in der Logger-Klasse zu setzen, damit die Formatierung nur stattfindet, wenn der Pegel geht. Sehr ähnlich ein eingebautes in sprintf. Zum Beispiel:

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

Das sollte Ihre Kriterien entspricht.

alt text http://www.scala-lang.org /sites/default/files/newsflash_logo.png

Scala hat eine annontation @ elidable () , die Sie Methoden mit einem Compiler-Flag entfernen können.

Mit dem scala REPL:

  

C:> scala

     

Willkommen in Scala Version 2.8.0.final (Java HotSpot (TM) 64-Bit Server VM, Java 1.   6.0_16).   Geben Sie in Ausdrücken sie ausgewertet haben.   Typ:. Weitere Informationen helfen

     

scala> Import scala.annotation.elidable   Import scala.annotation.elidable

     

scala> Import scala.annotation.elidable._   Import scala.annotation.elidable ._

     

scala> @elidable (FINE) def logDebug (arg: String) = println (ARG)

     

logDebug: (arg: String) Einheit

     

scala> logDebug ( "testing")

     

scala>

Mit elide-beloset

  

C:> scala -Xelide-unter 0

     

Willkommen in Scala Version 2.8.0.final (Java HotSpot (TM) 64-Bit Server VM, Java 1.   6.0_16).   Geben Sie in Ausdrücken sie ausgewertet haben.   Typ:. Weitere Informationen helfen

     

scala> Import scala.annotation.elidable   Import scala.annotation.elidable

     

scala> Import scala.annotation.elidable._   Import scala.annotation.elidable ._

     

scala> @elidable (FINE) def logDebug (arg: String) = println (ARG)

     

logDebug: (arg: String) Einheit

     

scala> logDebug ( "testing")

     

Test

     

scala>

Siehe auch Scala Definition behauptet

Conditional Logging ist böse. Sie fügt hinzu, unnötige Unordnung in Ihren Code ein.

Sie sollten immer in den Objekten senden Sie an den Logger haben:

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

und haben dann eine java.util.logging.Formatter, die Message foo abzuflachen verwendet und eine Bar in den String ausgegeben werden. Es wird nur dann aufgerufen werden, wenn der Logger und Handler auf dieser Ebene log wird.

Für zusätzliche Freude konnte man eine Art Ausdruckssprache muß in der Lage sein, eine Feinsteuerung zu bekommen, wie die protokollierten Objekte zu formatieren (toString nicht immer sinnvoll sein kann).

So viel wie ich hasse Makros in C / C ++, bei der Arbeit haben wir #defines für das, wenn ein Teil, der bei falscher ignoriert (nicht ausgewertet) die folgenden Ausdrücke, aber wenn sie wahr liefert einen Strom, in den Stoff geleitet werden kann mit dem '<<' Operator. Wie folgt aus:

LOGGER(LEVEL_INFO) << "A String";

Ich nehme an, das die Extra ‚Komplexität‘ beseitigen würde, die Ihr Werkzeug sieht, und beseitigt auch jede Berechnung des Strings oder irgendwelche Ausdrücke angemeldet sein, wenn das Niveau nicht erreicht wurde.

Hier ist eine elegante Lösung unter Verwendung von ternären Ausdruck

logger.info (logger.isInfoEnabled () "Log Statement geht hier ...?": Null);

eine Logging-util Funktion Betrachten ...

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

Dann der Anruf mit einem „Verschluss“ um die teure Auswertung, die Sie vermeiden möchten.

debugUtil(“We got a %s”, new Object() {
       @Override String toString() { 
       // only evaluated if the debug statement is executed
           return expensiveCallToGetSomeValue().toString;
       }
    }
);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top