循環的複雑度が最小限の条件付きログ記録 質問する

循環的複雑度が最小限の条件付きログ記録 質問する

読んだあと "循環的複雑度の適切な制限は何ですか?「同僚の多くがこの新しい品質保証私たちのプロジェクトの方針:10以上循環的複雑度機能ごとに。

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

しかし、私たちの(200人 - 7年間の)プロジェクトの開始時には、私たちは喜んでログを記録していました(そして、それを簡単に「アスペクト指向プログラミング' ログに対するアプローチ)。

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

そして、私たちのシステムの最初のバージョンが稼働したとき、私たちは大きなメモリの問題を経験しました。それは、ログ記録(ある時点ではオフにされていました)が原因ではなく、ログパラメータ(文字列) は常に計算され、その後 'info()' または 'fine()' 関数に渡されますが、ログ記録のレベルが 'OFF' であり、ログ記録が行われていないことがわかります。

そこで QA 部門が戻ってきて、プログラマーに条件付きログ記録を常に行うように促しました。

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

しかし現在、関数ごとに 10 の循環的複雑度という「移動できない」制限があるため、各「if(isLoggable())」が +1 循環的複雑度としてカウントされるため、関数内に配置するさまざまなログが負担に感じられると主張しています。

つまり、関数に8つの「if」、「else」などがあり、1つの密結合された共有しにくいアルゴリズムと3つの重要なログアクションがある場合、条件付きログが制限を超えていないとしても、制限を超えてしまいます。本当にその機能の複雑さの一部は...

この状況にどう対処しますか?
私のプロジェクトでは、その「競合」が原因で、興味深いコーディングの進化がいくつか見られましたが、まずはあなたの考えを聞きたいです。


回答ありがとうございます。
問題は「フォーマット」ではなく「引数の評価」に関するものであると強調しなければなりません(何もしないメソッドを呼び出す直前に行う評価は、非常にコストがかかる可能性があります)。
したがって、a が上記の「文字列」と書いたとき、私が実際に意味していたのは aFunction() であり、aFunction() は文字列を返し、ロガーに表示されるあらゆる種類のログデータを収集して計算する複雑なメソッドの呼び出しです...またはそうではありません(これが問題であり、義務条件付きログ記録を使用するため、実際には「循環的複雑度」が人為的に増加するという問題が発生します...)

私は今、「可変長引数皆さんの何人かが提案した「function」ポイント(ありがとう、ジョン)。
注:Java6での簡単なテストでは、私の可変引数関数呼び出される前に引数を評価するため、関数呼び出しには適用できませんが、「ログ取得オブジェクト」(または「関数ラッパー」) には適用できます。この場合、toString() は必要な場合にのみ呼び出されます。わかりました。

このトピックに関する私の経験を投稿しました。
来週の火曜日に投票するまでそのままにしておき、その後、皆さんの回答の中から 1 つを選択します。
改めて、ご提案をいただきありがとうございました :)

ベストアンサー1

現在のログ記録フレームワークでは、この疑問は意味をなさない。

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 では、各ログ ステートメントは新しい を作成しStringBuildertoString()文字列に連結された各オブジェクトの メソッドを呼び出します。これらのtoString()メソッドは、次に、StringBuilder独自のインスタンスを作成し、toString()メンバーの メソッドを呼び出すなどして、潜在的に大きなオブジェクト グラフ全体にわたって処理を実行する可能性があります。(Java 5 より前では、 が使用され、そのすべての操作が同期されていたため、さらにコストがかかっていましたStringBuffer。)

これは、特にログ ステートメントが頻繁に実行されるコード パスにある場合、比較的コストがかかる可能性があります。また、上記のように記述すると、ログ レベルが高すぎるためにロガーが結果を破棄しなければならない場合でも、コストのかかるメッセージのフォーマットが行われます。

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

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

dこのガードにより、引数とw文字列の連結の評価は必要な場合にのみ実行されます。

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

ただし、ロガー (または選択したロギング パッケージの周りに記述したラッパー) がフォーマッタとフォーマッタの引数を受け取る場合、ガード ステートメントとその循環的複雑性を排除しながら、メッセージの構築は、それが使用されることが確実になるまで遅延できます。

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 での 1 つの小さなペナルティは、ロガーに渡すプリミティブ型引数の自動ボックス化です。

乱雑な文字列の連結がなくなったため、ログ記録を行うコードはこれまで以上にクリーンになったと言えます。書式文字列を外部化 ( を使用ResourceBundle) すると、さらにクリーンになり、ソフトウェアのメンテナンスやローカリゼーションにも役立ちます。

さらなる機能強化

また、Java では、MessageFormat「フォーマット」の代わりにオブジェクトを使用できますString。これにより、基数をもっと適切に処理するための選択フォーマットなどの追加機能が提供されます。別の方法としては、基本メソッドではなく、「評価」用に定義したインターフェイスを呼び出す独自のフォーマット機能を実装する方法がありますtoString()

おすすめ記事