.NET で独自の AOP を導入したら不便になってしまった話


ロギングのコードを毎度書かなくてよいって素晴らしいじゃないですか？
それではアスペクト指向プログラミング (AOP) を開発に取り入れよう。と思い、AOP をプロジェクトに導入してしたら不便になってしまった話をします。


## AOP の実装方法

.NET で AOP を実装する方法をいくつかご紹介します。

- RealProxy クラスを使用して、独自に実装する方法
- Unity Container (Unity) ライブラリを使用する方法
- PostSharp ライブラリを使用する方法（商用）

可能な限りライブラリに依存しない方法で進めていくプロジェクトだったので、今回は RealProxy クラスを使って実装することにしました。
参考にした URL を記載しておきます。

[.NETにおいてAOPを実現する透過プロキシ - マイクロソフト系技術情報 Wiki](https://techinfoofmicrosofttech.osscons.jp/index.php?.NETにおいてAOPを実現する透過プロキシ)

[アスペクト指向プログラミング - RealProxy クラスによるアスペクト指向プログラミング](https://msdn.microsoft.com/ja-jp/ja-sp/magazine/dn574804.aspx)

[UnityでAOPをしよう in C# for Visual Studio 2012](https://code.msdn.microsoft.com/windowsdesktop/UnityAOP-0ff53983)

[C#のAOPライブラリ（PostSharp） - Status Code 303 - See Other](http://kouki-hoshi.hatenablog.com/entry/2017/06/21/023159)


## 出来上がったコードたち

載せるコードを短くしたかったので簡略化しています。実際に使用していたときはメソッドのパラメーターや戻り値などをログファイルに出力したり、ロギング以外のプロキシも復数実装していました。

#### ロギングプロキシ

```csharp
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);
    }
}
```

#### プロキシを使用可能にするクラス

```csharp
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!";
    }
}
```

#### 実際に使用しているクラス

```csharp
static void Main(string[] args)
{
    var test = LogProxy<TestModel>.Create(new TestModel());
    test.MethodA("123");
    test.MethodB("ABC");
}
```

#### 出力結果
<img src="https://i.gyazo.com/9bf2d4463c8baf179f85ae007ed00e51.png" alt="https://gyazo.com/9bf2d4463c8baf179f85ae007ed00e51" width="679"/>


## 開発が進むにつれて邪魔になってきた AOP

出来上がったコードだけを見ると自動でログを書いてくれてとても便利です。他にも便利なプロキシクラスをたくさん増やしていきました。
しかし、便利なものが増えていくにつれて問題が発生していきます。その中でも特に厄介だったのがこの部分になります。

#### デバッグがしづらくなってきた

デバッグ時にステップインしているとプロキシクラスに飛ばされてしまいます。
メソッド等に対してステップインするときは、そのメソッドの処理に入りたいはずです。プロキシに飛ばされるのは凄くストレスです。

<img src="https://i.gyazo.com/e21e8bb53666922c520e429005636e4c.png" alt="https://gyazo.com/e21e8bb53666922c520e429005636e4c" width="813"/>

ウォッチウィンドウなどでクラスの状態を見ようとしても、呼び出し元から中身を見ることができません。

<img src="https://i.gyazo.com/fd54ee7445e9a06498c4b5da6f4ed31b.png" alt="https://gyazo.com/fd54ee7445e9a06498c4b5da6f4ed31b" width="925"/>


## 完成したアプリケーション

コード上から AOP はほぼ消えました。そしてロギングは必要な時にそれぞれのクラスで実装するようになりました。残っているのは邪魔になってくる前にテストが完了してリリースしてしまった部分になります。残念ながら技術的負債になってしまいました。


## どうすればよかったのか

まとめです。独自に作成した場合と、ライブラリを使用した場合の二つに分けました。上手く開発に成功していれば「こうするべき」というのを書きたかったのですが、今回は上手くいかなかったので気をつけるべき点だけ記載します。

#### RealProxy クラスを使用する場合

デバッグがしづらくなることを前提に開発を進めてください。それと MarshalByRefObject を使用しているため、パフォーマンスへの影響も考慮してください。
そういった色々な部分も考えながら開発を進めるのは面倒なので、個人的にはもう使いたくありません。

#### ライブラリを使用する場合

AOP のライブラリに限らず、ライブラリを使用する場合のメリット／デメリットを考慮した上で導入してください。RealProxy クラスがあまりにも微妙だったため、どちらかと言えばライブラリを使用する方法のほうが好きです。