ロギングポリシー

 ログは仕込む箇所や出力レベルを適切かつ明確に管理すべきと常々思っています。
 理由はログに出力される文字列には書式文字列が使われ、容量面や性能面でネックになりやすいからです。


 しかし、デバッグのことを考えると、場合によって出力されなくなることを避けるため、レベルを重要度の高い方に設定したりするなどで、個人任せでは徹底することは難しいと思います。


 また、あまりログを制限してしまったりすると、今度は必要なログが出力されなくなることで、デバッグが日常的に困難になったり、緊急性の高いバグについて、ログを見ても判断がつかなくなったりしてしまいます。


 そこで、単体テスト契約プログラミングを用いる場合、品質が保証できる箇所についてはログを入れる必要がなくなることを利用します。


 つまり、品質保証の確実性が高ければ高いほど、ログ出力レベルを低重要度となるようにすれば、ログを仕込むのに躊躇する必要はなく、適切にログを張り巡らせることができます。


 仮にそれでも多い場合が生じても、ログレベル単位での変更や高重要度のログのみを見直せば済むようになるため、仕込む場合に容量面や性能面を気にせず、また、デバッグ時にはレベルを低重要度に下げることで、デバッグ容易性も確保することが可能です。


 では、具体的に単体テスト契約プログラミングにより品質を保証できる箇所とそこでのログ出力頻度、出力レベルの考え方、いわばロギングポリシーについて、以下に述べていきます。


 まず、ソースコードでの関数、あるいはメソッドが果たす役割について考慮し、ロギングポリシーを考えます。
 以下は特にオブジェクト指向は前提ではありませんが、構造化プログラミングは前提となります。

インタフェース

 単体テスト契約プログラミングを実施した経験のある方は既にご存知と思いますが、外部との結合点となる部分、いわゆるインタフェースに関するテストは厄介なものです。


 スタブ、ドライバ、モックオブジェクトを駆使してテストしたものの、擬似化した箇所についての認識が外部と整合していなければ、結果的に品質を確保したことにならないため、結合してみると一部に不正が生じたり、最悪の場合、全体に影響するようなバグを出してしまいます。


 すなわち、インタフェースに関わる箇所は品質保証の確実性が低い箇所であり、ログ出力レベルを高重要度に設定しておく必要があります。


 一方で、インタフェースは、内容によっては高頻度に通過することがあるため、性能面でのボトルネックに陥りやすいです。


 解消の方法は二つあり、一つは利用側と提供側の両方で仕掛けているケースを、どちらか一方で出力するか、一方の重要度を下げるように見直すことで、単純に半分にできます。
 通常は提供側の方から仕様を提示することになるため、提供側の方を高重要度とし、利用側を見直すことになります。


 ただ、隠蔽されたライブラリなどの場合には変更が比較的困難であり、提供側が必要なログを出力できない場合は利用側で補う必要がありますし、利用側が勝手にログを出力してしまう場合は、提供側を見直すなど、柔軟な対応をする必要があります。


 もう一つは正常系と異常系を明確にし、異常系の場合においてのみ、ログを仕込むか、高重要度にする方法です。
 ソースコード上、異常系の判断方法によっては容量面で問題が大きくなる可能性はありますが、それはソースコードの見直しで修正できますし、異常系は正常系と比べて頻度が低いという前提が成立する限り、性能面の向上は確実です。


 こちらの場合も隠蔽されたライブラリなどでソースコードを触れない場合は前者と同様の問題が生じますが、やはり柔軟に対応するしかありません。


 上記の二つの方法は併用可能ですので、両者とも適用する方が望ましいと思われます。

トランクライン、ブランチライン

 関数(あるいはメソッド)は、それぞれ相応の役割を持ちますが、内部だけの関わりで充分に品質を保証できる関数と、外部との関わりがあり、変更が必要になりやすいために、品質を確実に保証できない関数があります。


 このうち、外部*1から利用される箇所、及び外部を利用する箇所、そのために通過する関数をトランクライン関数、内部から利用され、内部を利用し続ける関数をブランチライン関数とここでは言います。


 抽象的な表現だけではわかりにくいと思いますので、もう少し具体的な例を挙げます。
 例えば、「画像のファイル名を渡して対応可能なフォーマットか判定する」インタフェースを、外部の「画像データ解析処理部」を利用することで、提供する場合に、以下のように実装するとします。

  1. インタフェース部
    1. 引数を受け取る
    2. 画像のファイル名が不正であればエラーを返す
  2. 内部処理部
    1. 画像ファイルを読み込み、失敗したらエラーを返す
    2. 外部処理部に画像データを渡して処理させる
    3. 画像データの対応可否を返す
  3. 外部処理部
    1. 画像データ解析処理部を画像データを渡して呼び出す
    2. 解析完了を受け、結果を返す

 このとき、1.1, 2.3が「外部から利用される箇所」、3.1, 3.2が「外部を利用する箇所」、2.2が「そのために通過する関数」に当てはまるため、これらをトランクライン関数と呼ぶということになります。


 また、一方で1.2, 2.1については内部で作成した関数を呼び出す形にすると、それらの関数は内部のみに関連するのでブランチライン関数と呼びます。実際、「画像データ解析処理部」を呼び出す直前のコールスタックには積まれません。


 この分類により、トランクラインは品質保証が難しく、重要度を高くすべきと判断できる一方で、ブランチライン関数は比較的品質保証が容易で、ログ出力上で重要度を下げられる関数であると判断できます。

今回のまとめ

 以上は役割からの分類によるポリシーであり、大規模開発では効果的な考え方ですし、小規模でも充分な効果があるはずです。
 ただし、役割的分類だけでは実際のデバッグに支障をきたす場合もあると思います。


 さらに、実際のデバッグを支援できる、技術的分類も記述しようかと思いましたが、長くなりましたので、次のエントリにて記述します。
 いずれにしても、先に構造化やオブジェクト指向により、結合度の低減と凝集度の増大を果たしておかないと、ロギングポリシーの効果も発揮されないので、その点は常に意識する必要があります。

*1:ここで言う外部というのはANSI標準関数のような言語上のプラットフォームは含みません。