[Language-Agnostic] サイクロマティックな複雑さを最小限に抑えた条件付きロギング


Answers

現在のロギングフレームワークでは、問題は疑問です

slf4jやlog4j 2のような現在のロギングフレームワークでは、ほとんどの場合ガードステートメントは必要ありません。 それらは、イベントが無条件にログに記録されるようにパラメータ化されたログステートメントを使用しますが、メッセージフォーマットはイベントが有効な場合にのみ発生します。 メッセージ構築は、アプリケーションによって先取りされるのではなく、必要に応じてロガーによって実行されます。

アンティークロギングライブラリを使用する必要がある場合は、を読んで、より多くの背景とパラメータ化されたメッセージで古いライブラリを改造する方法を得ることができます。

ガードステートメントは本当に複雑ですか?

サイコマティック複雑度計算からロギングガードステートメントを除外することを検討してください。

条件付きロギングチェックは、予測可能なフォームのため、コードの複雑さにはあまり貢献していないと主張できます。

柔軟性のないメトリックは、他の点では優れたプログラマが悪くなる可能性があります。 注意してください!

複雑さを計算するためのツールをその程度に合わせることができないと仮定すると、以下のアプローチが回避策を提供する可能性があります。

条件付きログの必要性

私はあなたのガードステートメントが導入されたと仮定します。

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

Javaでは、各logステートメントは新しいStringBuilder作成し、その文字列に連結された各オブジェクトに対してtoString()メソッドを呼び出します。 これらのtoString()メソッドは、独自のStringBuilderインスタンスを作成し、メンバーのtoString()メソッドなどを呼び出す可能性があります。 (Java 5より前では、 StringBufferが使用され、すべての操作が同期されていたため、さらに高価でした)。

これは、特にlog文が重度に実行されたコードパスにある場合、比較的コストがかかる可能性があります。 上記のように、ログレベルが高すぎるために結果を破棄するようにロガーがバインドされていても、高価なメッセージフォーマットが発生します。

これにより、次の形式のガードステートメントが導入されます。

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

このガードでは、引き数dw評価と文字列連結は、必要なときにのみ実行されます。

シンプルで効率的なロギングのソリューション

ただし、ロガー(または選択したロギングパッケージの周りに書き込むラッパー)がフォーマッターとフォーマッターの引数を取る場合、メッセージの構築は、ガードステートメントとそのサイクロマティックな複雑さ。

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

}

さて、 バッファ割り当てを伴うカスケードtoString()呼び出しは、必要でない限り発生しません! これにより、ガード・ステートメントにつながったパフォーマンス・ヒットが効果的に排除されます。 Javaでの小さなペナルティは、ロガーに渡すプリミティブ型引数の自動ボクシングです。

ロギングを行っているコードは、間違いなく文字列の連結がなくなっているので、これまで以上にきれいです。 書式文字列が( ResourceBundleを使用して)外部化されていれば、ソフトウェアのメンテナンスやローカライゼーションにも役立ちます。

さらなる強化

また、Javaでは、 "format" String代わりにMessageFormatオブジェクトを使用することができます。これにより、基本的な数値をよりきれいに扱うための選択形式などの追加機能が提供されます。 もう一つの方法は、基本的なtoString()メソッドではなく、 "評価"のために定義したインタフェースを呼び出す独自の書式設定機能を実装することです。

Question

サイクロ体の複雑さにはあなたの/限界がどれくらいあるのですか 」を読んだ後、私の同僚の多くは、私たちのプロジェクトに関するこの新しいQAポリシーに悩まされました。

意味: 'if'、 'else'、 'try'、 'catch'などのコードワークフロー分岐ステートメントは10個以下です。 右。 私が説明したように、 あなたはプライベートメソッドをテストしますか? '、そのような政策は多くの良い副作用を持っています。

しかし、私たち(200人〜7年)のプロジェクトの始めには、喜んでログを記録していました(いいえ、何らかの「 アスペクト指向プログラミング 」ログに簡単に委ねることはできません)。

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

システムの最初のバージョンが公開されたとき、我々は膨大なメモリ問題を経験しました(ある時点ではオフになっていた)ロギングではなく、常に計算されたログパラメータ (文字列)のためにロギングのレベルが「オフ」であり、ロギングが行われていないことを検出するためにのみ、 'info()'または 'fine()'関数を使用します。

だからQAが戻ってきて、私たちのプログラマーに条件付きロギングをさせるよう促した。 常に。

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

しかし、今や、関数の限界あたりの循環不可能な複雑さのレベルで、彼らはそれぞれの "if(isLoggable())"が+1サイクロマティックな複雑さとして数えられる!

したがって、関数が8つの 'if'、 'else'というように、密接に結合されていない簡単に共有できるアルゴリズムと3つのクリティカルなログアクションで...条件付きログが実際にはないその機能の複雑さの一部...

この状況にどう対処しますか?
私は私のプロジェクトで興味深いコーディングの進化を見たことがありますが(それは '葛藤'のため)、私はあなたの考えを最初にしたいと思います。

すべての答えをありがとう。
私は、問題は「フォーマット」関連ではなく、「引数評価」に関連していると主張しなければなりません(何もしない方法を呼び出す直前に非常にコストがかかる可能性があります)
だから、 "A String"の上に書いたとき、私は実際にaFunction()を意味し、aFunction()はStringを返し、ロガーによって表示されるすべての種類のログデータを収集して計算する複雑なメソッドへの呼び出しです... (したがって、問題と条件付きログを使用する義務 、したがって、サイクロマティックな複雑さの人為的増加の実際の問題...)

私は今あなたのいくつかによって進歩した「 variadic機能」のポイントを得ます(ジョンに感謝します)。
注意:java6のクイックテストは、私のvarargs関数が呼び出される前にその引数を評価するので、関数呼び出しには適用できませんが、 'Log retriever object'(または 'function wrapper')ではtoString( )は、必要な場合にのみ呼び出されます。 とった。

私は今このトピックに関する私の経験を投稿しました。
私は投票のために次の火曜日までそれをそこに残すでしょう、そして私はあなたの答えの一つを選択します。
再度、すべての提案に感謝します:)




たぶんこれは単純すぎるかもしれませんが、ガード句の周りの "抽出メソッド"リファクタリングの使用はどうですか? これのあなたのコード例:

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
}

これになる:

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



CまたはC ++では、条件付きロギングのif文の代わりにプリプロセッサを使用します。




私がC / C ++でマクロを嫌うのであれば、if部分には#defineがあります。falseなら次の式は無視されますが、trueの場合は ' <<演算子。 このような:

LOGGER(LEVEL_INFO) << "A String";

私は、これがあなたのツールが見る余分な「複雑さ」を排除し、文字列の計算や、レベルに達していない場合にログに記録される式を排除すると仮定します。




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

Scalaはコンパイラフラグを持つメソッドを削除するための@elidable()に関する@elidable()があります。

スカラREPLで:

C:>スカラー

Scalaバージョン2.8.0.final(Java HotSpot(TM)64ビットサーバーVM、Java 1. 6.0_16)へようこそ。 式を評価するには、式を入力します。 詳細については、タイプ:ヘルプを参照してください。

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)ユニット

scala> logDebug( "testing")

スカラ>

elide-belosetで

C:> scala -Xelide-below 0

Scalaバージョン2.8.0.final(Java HotSpot(TM)64ビットサーバーVM、Java 1. 6.0_16)へようこそ。 式を評価するには、式を入力します。 詳細については、タイプ:ヘルプを参照してください。

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)ユニット

scala> logDebug( "testing")

テスト

スカラ>

Scalaのアサーション定義もご覧ください




ロギングユーティリティ関数を考えてみましょう...

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

次に、回避したい高価な評価の回りに「閉鎖」をして電話をかけます。

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





Links