ロギングと監査は、 PostSharpが最高の機能の1つです。 これは、横断的な原則を示す基本的な例の1つです。 実際、これを行う必要があるすべての場所にログコードを挿入する代わりに、1つのポイントでのみログを入力し、必要なすべてのメソッドにコードを自動的に配布します。 そしてもちろん、いくつかのメソッド、フィールド、またはプロパティで選択的にこれを行うことができます。 非常に有用な情報をログに書き込むことができますが、フレームワークが存在し、そのフレームワークを使用している間に、次の一般的なカテゴリを強調します。
- 実行可能コードに関する情報:関数名、クラス名、パラメーター値など。 これにより、結果につながる可能性のあるさまざまなシナリオを大幅に削減できます。
- パフォーマンス情報:メソッドの実行にかかった時間を確認できます。
- 例外:例外をキャッチし、それに関する情報をログに保存し、元のアプリケーションの操作のロジックに違反しないようにこの例外を再生成することができます。
実際にはより技術的な問題であるロギング/トレーシングに加えて、ロギング/トレーシングに非常に似ているアプリケーションを監査することもできますが、監査はより多くの「ビジネス」アクティビティを運ぶ情報を追跡します。 たとえば、エラーを検索するときにパラメーターの値のログを取ることができます。または、あなたまたはマネージャーが、預金の操作が実行される頻度と時間を知りたい場合があります。 レポートファイルまたはデータベースのテーブルにすべての情報を入力して、美しいグラフの形式で企業のWebサイトに表示できます。
銀行の窓口プログラムを使用してみましょう。 そして、このアプリケーションが請求書カウンターを使用でき、WinFormsを使用して作成されていると仮定しましょう。 また、(非常に単純で単純化されたモデルでは)銀行に1人のクライアント(たとえば社長)のみを持ち、すべてのビジネスロジックを提供する静的クラスを1つだけ持つようにします。
public class BankAccount
{
static BankAccount()
{
Balance = 0;
}
public static decimal Balance { get ; private set ; }
public static void Deposit( decimal amount)
{
Balance += amount;
}
public static void Withdrawl( decimal amount)
{
Balance -= amount;
}
public static void Credit( decimal amount)
{
Balance += amount;
}
public static void Fee( decimal amount)
{
Balance -= amount;
}
}
* This source code was highlighted with Source Code Highlighter .
もちろん、実際のアプリケーションでは、静的クラスだけでなく、サービス、依存性注入、およびその他のアーキテクチャソリューションの特殊なレイヤーを使用します。 また、依存関係の "遅延読み込み"を使用したい場合もありますが、不要なものはすべて削除し、ログと監査という主なことに集中しましょう。
会社に誠実な従業員と顧客がいる限り、このアプリケーションはいつでもうまく機能します。 ただし、この銀行の財務ディレクターは解雇段階にあります。 数百万ドルが突然行方不明になり、誰もその理由を理解できません。 控えめなポストシャープスペシャリストであるあなたを雇い、あなたの助けで何が起こっているのかを把握します。 彼女は、すべての操作とトランザクションを追跡するようにプログラムを変更することを望んでいます(はい、会計がありますが、レポートに表示されるものではなく、プログラムの方法のレベルでこれを行いたい)。 このような状況では、忍耐強く、各メソッドにビジネスロジックプログラムを導入することができます(この例では、これらは4つのメソッドのみですが、実際のアプリケーションでは数千になる可能性があります)。 または、1つのメソッドのみを記述し、単調な猿の仕事なしで、クラスまたはメソッドの特定のグループのすべてのメソッドにすぐに適用できます。 さらに、プログラム全体にコードを配置すると、後で削除する必要があるため(またはリリースビルドに含まれないように追加の手順を実行する必要があるため)、自分でトラップを設定します。 そして、このアプローチの不便さの2番目の理由は、あなたがそれを書いている間に間違いを犯すことができるということです。 PostSharpを使用して記述するコードを見てみましょう。
[ Serializable ]
public class TransactionAuditAttribute : OnMethodBoundaryAspect
{
private string _methodName;
private Type _className;
private int _amountParameterIndex = -1;
public override bool CompileTimeValidate(MethodBase method)
{
if (_amountParameterIndex == -1)
{
Message.Write(SeverityType.Warning, "999" ,
"TransactionAudit was unable to find an audit 'amount' in {0}.{1}" ,
_className, _methodName);
return false ;
}
return true ;
}
public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
_methodName = method.Name;
_className = method.DeclaringType;
var parameters = method.GetParameters();
for ( int i=0;i<parameters.Length;i++)
{
if (parameters[i].Name == "amount" )
{
_amountParameterIndex = i;
}
}
}
public override void OnEntry(MethodExecutionArgs args)
{
if (_amountParameterIndex != -1)
{
Logger.Write(_className + "." + _methodName + ", amount: "
+ args.Arguments[_amountParameterIndex]);
}
}
}
* This source code was highlighted with Source Code Highlighter .
CompileTimeInitializeを使用して、コンパイル中にメソッドとパラメーターの名前を取得し、アプリケーション操作中に使用されるリフレクションの量を最小限に抑えます(ところで、ビルド時のコードを使用してリフレクションの使用をゼロに減らすことができます)。それに従います。 見つからない場合は、警告を使用して開発者に通知します。
この側面とある種のストレージを使用して、収集されたデータを保存することにより、CFOの分析情報を作成できます。
ただし、調査の進行中に、システムに他の問題がある可能性があることを理解し始めます。たとえば、ユーザーインターフェイスが安定しておらず、アプリケーションが常に「クラッシュ」していることがわかります。 クラッシュの場所を見つけるには、try / catchをプログラムのさまざまな場所(膨大な数になる場合があります)に配置して、どの特定の場所が誤動作しているかを理解します。 または、1つのクラスを記述すると、すべてのインターフェイスメソッドで例外監査が自動的に有効になります(はい、このクラスのスコープを簡単に制限できます)。 根拠がないように、簡単な例を見てみましょう。
[ Serializable ]
public class ExceptionLoggerAttribute : OnExceptionAspect
{
private string _methodName;
private Type _className;
public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
{
_methodName = method.Name;
_className = method.DeclaringType;
}
public override bool CompileTimeValidate(MethodBase method)
{
if (! typeof (Form).IsAssignableFrom(method.DeclaringType))
{
Message.Write(SeverityType.Error, "003" ,
"ExceptionLogger can only be used on Form methods in {0}.{1}" ,
method.DeclaringType.BaseType, method.Name);
return false ;
}
return true ;
}
public override void OnException(MethodExecutionArgs args)
{
Logger.Write(_className + "." + _methodName + " - " + args.Exception);
MessageBox.Show( "There was an error!" );
args.FlowBehavior = FlowBehavior.Return;
}
}
* This source code was highlighted with Source Code Highlighter .
そして、繰り返しますが、CompileTimeInitializeはリフレクションの呼び出し回数を減らすためだけに使用します。 (いくつかの機能に)この側面を適用するには、対応するメソッド/クラス/アセンブリ/名前空間をマークする必要があります(この場合、アセンブリがマークされ、アセンブリメンバーのフルネームによる追加のフィルターが指定されます):
クラスをマークするには、クラス自体をマークするか、クラス名を指定してアセンブリをマークします。
// in AssemblyInfo.cs
[assembly: ExceptionLogger(AttributeTargetTypes = "LoggingAuditing.BankAccountManager" )]
* This source code was highlighted with Source Code Highlighter .
ロギングをオンにしてアプリケーションを監査した後、ひどいことが判明しました。
- クライアントは自分のアカウントからマイナスの金額を引き出すことができますが、正直でないクライアントはこれをよく使用します!
- インターフェイスフォームに情報を入力するとき、入力を確認せずに常にタイプミスをします。 たとえば、レジ係が「$ 5.19」と入力すると、未処理の例外が発生し、アプリケーション全体がクラッシュします!
2つの非常に単純な側面を使用して、アプリケーションのこれらの明白な欠点を十分に迅速に解決し、ビジネスユーザーにトランザクションフローの監査を提供し、チームの開発者が出荷およびユーザーエクスペリエンス中の例外的な状況をログに記録して監視できるようにします。 インストール段階、ユーザーの作業段階でハードブレークされたアプリケーションは、膨大な数の問題を引き起こす可能性があることを認識する必要があります。 特に開発チームと。 ただし、アスペクトを使用すると、非常に迅速に診断および修正できます。
それでは、内部を見て、実際に生成されるコードを見てみましょう。 それでも理解できなくてもてないでください。 PostSharpは非常に使いやすく、その結果は逆アセンブラーで簡単に開くことができます。 とにかく、結果のコードを見てみましょう。 私たちはそれを理解したいと思います。
以下に、PostSharpを使用しない「クレジット」メソッドを示します。 ご覧のとおり、非常に簡単です。
public static void Credit( decimal amount)
{
Balance += amount;
}
* This source code was highlighted with Source Code Highlighter .
次に、TransactionAuditアスペクトを適用した後、同じメソッドを見てください。 このコードは、結果のアセンブリ(dllおよびexeファイル)にのみ含まれ、ソースには含まれないことに注意してください。
public static void Credit( decimal amount)
{
Arguments CS$0$0__args = new Arguments();
CS$0$0__args.Arg0 = amount;
MethodExecutionArgs CS$0$1__aspectArgs = new MethodExecutionArgs( null , CS$0$0__args);
CS$0$1__aspectArgs.Method = <>z__Aspects.m11;
<>z__Aspects.a14.OnEntry(CS$0$1__aspectArgs);
if (CS$0$1__aspectArgs.FlowBehavior != FlowBehavior.Return)
{
try
{
Balance += amount;
<>z__Aspects.a14.OnSuccess(CS$0$1__aspectArgs);
}
catch (Exception CS$0$3__exception)
{
CS$0$1__aspectArgs.Exception = CS$0$3__exception;
<>z__Aspects.a14.OnException(CS$0$1__aspectArgs);
CS$0$1__aspectArgs.Exception = null ;
switch (CS$0$1__aspectArgs.FlowBehavior)
{
case FlowBehavior.Continue:
case FlowBehavior.Return:
return ;
}
throw ;
}
finally
{
<>z__Aspects.a14.OnExit(CS$0$1__aspectArgs);
}
}
}
* This source code was highlighted with Source Code Highlighter .
そして再び:パニックにならないでください! :)このコードは、自動生成された変数名を使用しているために面倒に見えるかもしれませんが、実際には非常に簡単です。 try / catchのラッパーが元のコードに追加され、特定のアスペクトのコードが呼び出されます。 ご覧のとおり、ここでは、オーバーライドされたonEntryメソッドのみが実際に使用され、アクションを実行します。 ただし、オーバーライドが必要な問題を解決する場合は、他のメソッド(onExit、onSuccess、onException)をオーバーライドできます。
上記で与えられたこのコードは、postsharpの無料バージョンを生成します。 以下に示すように、プログラムの完全に機能するバージョンは、結果のコードを最適化することにより機能します。 この場合、プログラムは、onEntryメソッドのみを使用し、この場合、大量のコードを生成する必要がないことを理解します。 この場合、次のような短いコードを取得できます。
public static void Credit( decimal amount)
{
Arguments CS$0$0__args = new Arguments();
CS$0$0__args.Arg0 = amount;
MethodExecutionArgs CS$0$1__aspectArgs = new MethodExecutionArgs( null , CS$0$0__args);
<>z__Aspects.a14.OnEntry(CS$0$1__aspectArgs);
Balance += amount;
}
* This source code was highlighted with Source Code Highlighter .
完全に機能するバージョンは、必要なコードのみをインテリジェントに生成します。 postharpの側面を最大限に活用するアプリケーションを作成している場合、フル機能バージョンを使用することは、アプリケーションのパフォーマンスを向上させるための優れたソリューションです。
ただし、自動生成されたクラスメンバとローカル変数の面白い名前にもかかわらず、PostSharpが何であるかをよく理解していただければ幸いです。
参照: