はじめに
私にとってGUIアプリケーションのデバッグとは、ほとんどの場合、デバッグステートメントをダイアログボックスの形式で表示することだ。このテクニックは小規模から中規模のアプリケーションでは有効だ。しかし大規模なアプリケーションの場合、ステートメントごとにダイアログボックスがポップアップさせるのは非生産的だ。そこで、実行時にデバッグステートメントを表示するもっと良い方法はないかと考えた結果、目に留まったのがC#である。
C#は、私が便利でスケーラブルなデバッグシステムの設計に取り組んでいるときにぶつかった3つの問題を解決してくれた。私は普段JavaやC/C++を主に使用しているが、これらのプログラミング言語では次のような問題があった。
- 行番号、メソッド名などのメタ情報が十分に得られない
- デバッグの焦点が移るたびに、デバッグステートメントの追加と削除を行う必要がある
- デバッグステートメントをプログラム内にコンパイルするとパフォーマンスに悪影響が出る
3つの問題の解決策
では、これらの問題に対する解決策を順番に見ていこう。まずは1番目の問題である。この問題は、基本的にSystem.Reflection
およびSystem.Diagnostics
名前空間のいくつかのクラスによって解決できる。System.Diagnostics.StackFrame
クラスを使用すると、コールスタックを調査して、現在のレベルよりも上のレベルに何があるか、どの行から関数が呼び出されたか、などの詳細を知ることができる。また、System.Reflection
クラスを使用すると、関数、名前空間、変数型などの名前を確認できる。これらのクラスを利用したのが次のコードである。このコードでは、それを呼び出した関数のファイル名、行番号、メソッド名を取得している。
// create the stack frame for the function that // called this function StackFrame sf = new StackFrame( 1, true ); // save the method name string methodName = sf.GetMethod().ToString(); // save the file name string fileName = sf.GetFileName(); // save the line number int lineNumber = sf.GetFileLineNumber();
次は2番目の問題、実行時にプログラム内のさまざまなセクションを選択してデバッグするにはどうしたらいいかだ。この問題は、1番目の問題に関係している。1番目の問題を解決することで得られる情報が、デバッグステートメントの表示をフィルタリングするのに役立つからだ。次の例では、名前空間に基づいてフィルタリングを行うことにする。例えば、プログラム内に15の名前空間があり、そのうち1つの名前空間のデバッグステートメントだけを表示したいとする。この場合、デバッグクラスに、その名前空間だけがデバッグステートメントを表示するように指示するだけでよい。
コード例は次の通り。
// create the namespaces hashtable namespaces = new Hashtable(); // get the assembly of this class Assembly a = Assembly.GetAssembly( new Debug().GetType() ); // now cycle through each type and gather up all the namespaces foreach( Type type in a.GetTypes() ) { // check if the namespace is already in our table if( ! namespaces.Contains( type.Namespace ) ) namespaces.Add( type.Namespace, true ); }
上記のコードでは、このコードを含んでいるDebug
クラスと同じアセンブリ内にあるすべての名前空間を格納するHashtable
オブジェクトを作成する。しかし、これでは問題の半分が解決しただけである。残りの半分、つまり実際のフィルタリングコードは次のようになる。
// only proceed if the namespace in question is being debugged if( (bool) namespaces[ method.DeclaringType.Namespace ] ) // this is where the debug statement display code would be
ここまでは上出来だ。残る問題は、プログラムの最終リリースを作成するときに、厄介なデバッグステートメントを、1つ1つ手作業でコメントアウトするか削除するような手間をかけずに削除することだ。ここで役に立つのが"属性"だ。ここでは皆さんが既にC#における属性の動作を知っているものと想定して概念的な説明を省き、前述のSystem.Diagnostics
名前空間に含まれているConditionalAttribute
の説明に進みたいと思う。
次に、ConditionalAttribute
クラスの使用例を示す。
[Conditional("DEBUG")] public void Debug( string msg ) { // this is where the debug statement // display code would be }
このように記述すると、プログラムのコンパイル時に、#defined
で'DEBUG'
が指定されているかどうかがチェックされ、定義されている場合はこの関数がそのまま残される。しかし、#defined
で'DEBUG'
が指定されていない場合は、この関数に対するすべての呼び出しがコンパイルから除外され、パフォーマンスに影響を与えることがなくなる。
まとめ
ここで取り上げた問題のうち、いくつかはC/C++やJavaでも解決できると主張する人がいるかもしれないが、どちらの言語もすべての問題を解決することはできない。たとえばC/C++では、#define
と#ifdef
を使用して、C#のConditionalAttribute
クラスと同様のことを実現できる。しかし、C/C++で有益なメタ情報を入手するには限界がある。また、Javaではメタ情報を入手することは可能だが、私の知る限り、すべてのコードが必ずコンパイルされてしまう(つまり、条件コンパイルの機能はない)。C#ならば、上記の問題をすべて(しかも洗練された方法で)解決し、なおかつそれ以上のことが可能である。
以上のテクニックの具体例を示すために、小さなWindowsアプリケーションを作成してみた。このDebug
クラスは他のプロジェクトにも挿入可能である。ぜひお試しいただきたい。