ロギングのコードを毎度書かなくてよいって素晴らしいじゃないですか?
それではアスペクト指向プログラミング (AOP) を開発に取り入れよう。と思い、AOP をプロジェクトに導入してしたら不便になってしまった話をします。
AOP の実装方法
.NET で AOP を実装する方法をいくつかご紹介します。
- RealProxy クラスを使用して、独自に実装する方法
- Unity Container (Unity) ライブラリを使用する方法
- PostSharp ライブラリを使用する方法(商用)
可能な限りライブラリに依存しない方法で進めていくプロジェクトだったので、今回は RealProxy クラスを使って実装することにしました。
参考にした URL を記載しておきます。
.NETにおいてAOPを実現する透過プロキシ - マイクロソフト系技術情報 Wiki
アスペクト指向プログラミング - RealProxy クラスによるアスペクト指向プログラミング
UnityでAOPをしよう in C# for Visual Studio 2012
C#のAOPライブラリ(PostSharp) - Status Code 303 - See Other
出来上がったコードたち
載せるコードを短くしたかったので簡略化しています。実際に使用していたときはメソッドのパラメーターや戻り値などをログファイルに出力したり、ロギング以外のプロキシも復数実装していました。
ロギングプロキシ
public class LogProxy<T> : RealProxy
{
private readonly T _decorated;
private LogProxy(T decorated) : base(typeof(T))
{
_decorated = decorated;
}
public static T Create(T instanse)
{
return (T)new LogProxy<T>(instanse).GetTransparentProxy();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
Console.WriteLine($"Before executing '{methodCall.MethodName}'");
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
Console.WriteLine($"After executing '{methodCall.MethodName}' ");
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
}
}
プロキシを使用可能にするクラス
public class TestModel : MarshalByRefObject
{
public string MethodA(string param)
{
Console.WriteLine($"Testing {param}!");
return "MethodA OK!";
}
public string MethodB(string param)
{
Console.WriteLine($"Testing {param}!");
return "MethodB OK!";
}
}
実際に使用しているクラス
static void Main(string[] args)
{
var test = LogProxy<TestModel>.Create(new TestModel());
test.MethodA("123");
test.MethodB("ABC");
}
出力結果
開発が進むにつれて邪魔になってきた AOP
出来上がったコードだけを見ると自動でログを書いてくれてとても便利です。他にも便利なプロキシクラスをたくさん増やしていきました。
しかし、便利なものが増えていくにつれて問題が発生していきます。その中でも特に厄介だったのがこの部分になります。
デバッグがしづらくなってきた
デバッグ時にステップインしているとプロキシクラスに飛ばされてしまいます。
メソッド等に対してステップインするときは、そのメソッドの処理に入りたいはずです。プロキシに飛ばされるのは凄くストレスです。
ウォッチウィンドウなどでクラスの状態を見ようとしても、呼び出し元から中身を見ることができません。
完成したアプリケーション
コード上から AOP はほぼ消えました。そしてロギングは必要な時にそれぞれのクラスで実装するようになりました。残っているのは邪魔になってくる前にテストが完了してリリースしてしまった部分になります。残念ながら技術的負債になってしまいました。
どうすればよかったのか
まとめです。独自に作成した場合と、ライブラリを使用した場合の二つに分けました。上手く開発に成功していれば「こうするべき」というのを書きたかったのですが、今回は上手くいかなかったので気をつけるべき点だけ記載します。
RealProxy クラスを使用する場合
デバッグがしづらくなることを前提に開発を進めてください。それと MarshalByRefObject を使用しているため、パフォーマンスへの影響も考慮してください。
そういった色々な部分も考えながら開発を進めるのは面倒なので、個人的にはもう使いたくありません。
ライブラリを使用する場合
AOP のライブラリに限らず、ライブラリを使用する場合のメリット/デメリットを考慮した上で導入してください。RealProxy クラスがあまりにも微妙だったため、どちらかと言えばライブラリを使用する方法のほうが好きです。