printデバッグをしていると、出力が多すぎて困る事がある。
単にif文を入れて済めば良いが、それでは制御しきれない事も多々ある。
その様なケースでの、幾つか対処法を紹介する。
note
- 言語は C++ を想定しているが、他の言語でも流用は出来るはす。
エラー発生の前でのみデバッグ
状況設定
- 何度目かの呼び出しで、エラーが発生する。
- 他の関数を挟んで呼び出しているため、呼び元(ループ側)での制御はしずらい。
- エラーが発生する少し前の状態を知りたい。
void fn_3()
{
...
Trace(data); // 変更前の、この時点での情報が欲しい。
... // data が変更される。
data.foo(); // ここでエラーが発生する。
}
void fn_2(){ fn_3(); }
void fn_1(){ fn_2(); }
void fn()
{
for(...){
fn_1();
}
}
解決策
関数内stasic変数で、関数の呼び出し回数が分かるようにし、
エラー発生時点での関数の呼び出しが何回目かを記録する。
void fn_3()
{
+ static int debug_counter = 0;
+ ++debug_counter;
...
+ Trace(debug_counter); // エラー発生のタイミングを確認
data.foo(); // エラー発生時に、何らかの方法で処理を止める。
}
何度目の関数呼び出しでエラーが発生するかが分かったら、
そのタイミングでのみ、Traceさせる。
void fn_3(){
+ static int debug_counter = 0;
+ ++debug_counter;
...
+ if ( counter == 193 ) { // 事前に確認した値。
Trace(data);
+ }
...
data.foo();
}
継承先でのデバッグ
状況設定
以下の様に、ID番号を共通の親クラスで管理しているとする。
class Element {
public:
void SetID( const int id ) { m_id = id; }
...
private:
int m_id{};
};
class Impl_A : public Element {...};
class Impl_B : public Element {...};
class Impl_C : public Element {...};
Impl_A
での m_id の値が可怪しいので、SetID
のタイミングでデバッグをしたいが、
SetID
にトレース文を入れると、Impl_B
,Impl_C
の SetID
でもログが出てしまう。
解決策
- SetID を仮想関数にし、Impl_A でのみオーバーライドをする。
- 実装は、親の関数を呼ぶようにする。
class Common {
public:
- void SetID( const int id ) { m_id = id; }
+ virtual void SetID( const int id ) { m_id = id; }
...
private:
int m_id{};
};
class Impl_A : public Common {
+ void SetID( const int id ) override {
+ Trace( id );
+ Common::SetID(id);
+ }
...
};
class Impl_B : public Common {...};
class Impl_C : public Common {...};
特定呼び出しからのデバッグ
状況設定
特定の場所から呼ばれた時のみ、デバッグをしたい。
void fn_3( ... )
{
Trace(...); // fn_A から呼ばれた時のみ、Trace したい。
}
void fn_2( ... ){ fn_3( ... ); }
void fn_1( ... ){ fn_2( ... ); }
void fn_A() { fn_1( ... ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }
引数にフラグを追加する、では、変更量が多くて大変。
- オーバーロードが有ると、処理を変えてしまう可能性もある。
void fn_3( ..., const bool debug_flag = false )
{
if ( debug_flag ) {
Trace(...); // fn_A から呼ばれた時のみ、Trace したい。
}
}
void fn_2( ..., const bool debug_flag = false )
{
fn_3( ..., debug_flag );
}
void fn_1( ..., const bool debug_flag = false )
{
fn_2( ..., debug_flag );
}
void fn_A() { fn_1( ..., true ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }
解決策
コードをコピーし、同じ実装の、別の関数を作れば良い。
void fn_3( ... )
{
}
void fn_2( ... ){ fn_3( ... ); }
void fn_1( ... ){ fn_2( ... ); }
+namespace ForDebug {
+
+void fn_3( ... )
+{
+ Trace(...);
+}
+
+void fn_2( ... ){ fn_3( ... ); }
+void fn_1( ... ){ fn_2( ... ); }
+
+} // ForDebug
-void fn_A() { fn_1( ... ); }
+void fn_A() { ForDebug::fn_1( ... ); }
void fn_B() { fn_1( ... ); }
void fn_C() { fn_1( ... ); }
- ビルドが通らない場合は、関連コードを片っ端からコピーして持ってくる。
- static 変数が絡む場合は、処理が変わってしまう事に留意。
おまけ
- 通常は使用を避けるべき、コードのコピーやstatic変数だが、この様な場合は便利に使え。る
- デバッグ用の使い捨てコードなので、諸々のデメリットも気にしなくて済む。
- デバッグ用のコードで使う識別子は、うっかり Push してしまった場合に備えて、
それと分かる様な名前にしておくとベター。