C#コードのカスタム分析の経験

かなり前に、ファイナライザーでコードをテストする問題を共有し、最近テストのクラッシュについて不満を言いました( ファイナライザーコードのテスト方法(c#)ファイナライザーコードのテスト方法(c#)。その後:テストはまだクラッシュしました )。

Komittensとの議論の過程で、アイデアが表明されました。

ファイナライザ(IDisposableパターンが正しく実装されている場合)は、Dispose(false)を呼び出す必要があります(すべきです)。 この事実は、静的分析によってテストできます。 したがって、Dispose(false)が原因でファイルが削除された場合(テストを作成しましたか?)、ファイナライザーがファイルを削除することを確認できる場合、単体テストは冗長です。


この考えは私にとって非常に堅牢であるように思われました。さらに、組み込みのコード分析や再シャーパーが提供するよりもカスタムコードをカスタムに制御したいことがあります。

cat + "ozcodeが外部ライブラリの調査プロセスでどのように役立ったか"の下でコード分析用のカスタムルールを実装した経験





まず、探したいもののリストを拡張しました:



特定のタイプのフィールドを確認する



最も単純なものから始めることにしました。nlogWebサイトで推奨されているように、特定のクラスのフィールドとして宣言されている場合、ログオブジェクトは静的である必要があります。

したがって、最初の行はルールに適合し、2行目は適合しません。

private static ILog m_logger1 = LogManager.GetInstance(); private ILog m_logger2 = LogManager.GetInstance();
      
      







ファイナライザは「Dispose(false)」を呼び出す必要があります



実際、ファイナライザーで単体テストを記述しないためには、個別にテストされるDispose(false)を呼び出すことを確認するだけで十分です。



実装されたサービスは、サービスマネージャーに登録されます。



同僚の1人は、あるチームの人が特定のサービスを実装してテストしますが、それをサービスマネージャーに登録するのを忘れると、別のチームの人がこのサービスを使用しようとして、サービスが見つからないというエラーを受け取ることを指摘しました。








したがって、静的コード分析用の既存のツールを使用して、独自のルールをリリースするタスクを自分で設定しました。 子猫についても同じことが私にアドバイスした最初のツールは、標準の組み込みコード分​​析でした。



結局のところ、ルールを入力するためのしきい値は非常に低くなっています。 少なくとも、 カスタムの静的コード分析ルールを記述する方法を読み、 それらをVisual Studio 2010の投稿に統合してください 。 投稿ではVS2010について説明していますが、VS2013に実装したため、特別な問題は発生しませんでした。 この投稿には少なくとも1つのタイプミスがあり、さらに興味深いことに、私はルールをスタジオ自体に正常に統合することができませんでした:ルールは表示されるか表示されません(私を悩ませますが、あまり気にしませんコマンドラインからのテストの一部としてルールを実行します)。



そのため、 ここにステップバイステップの手順がありますので、ルール( githubのソースコード)を実装することから始めます。



特定のタイプのフィールドを確認する





非常にシンプル:Check(メンバーメンバー)と呼ばれる仮想メソッドが実装され、メンバーを受け取ります(Google翻訳者はメンバーを「メンバー」として翻訳しますが、リスクは負いません)、このメンバーが(Fieldとして)クラスフィールドであることを確認し、フィールドはログ(タイプ名ごとに愚かです)であり、このフィールドが静的かどうかを確認します。 そうでない場合は、「新しい問題」を作成して、問題リストに貼り付けます。



  public override ProblemCollection Check(Member member) { Field field = member as Field; if (field == null) { // This rule only applies to fields. // Return a null ProblemCollection so no violations are reported for this member. return null; } string actualType = field.Type.FullName; if (actualType == "SamplesForCodeAnalysis.ILog") { if (!field.IsStatic) { Resolution resolution = GetResolution(field, actualType); Problem problem = new Problem(resolution); Problems.Add(problem); } } return Problems; }
      
      







ファイナライザは「Dispose(false)」を呼び出す必要があります





このタスクは、メソッド全体をチェックする必要があるため、より興味深いものです。 私はこれを非常に部分的に実装し、きれいに試してみました:



通常どおり開始します。メンバーを受け取る仮想メソッドCheck(メンバーメンバー)を実装し、このメンバーがメソッド(メソッドとして)であることを確認し、ファイナライザーであることを確認します(「.Finalize」のメソッドの最後)。 次に、このメソッド「method.Instruction」の命令のリストを取得し、「。Dispose(System.Boolean)」で終わる命令が少なくとも1つあるかどうかを調べます。 もちろん、これは必要なものではありませんが、少なくともDispose呼び出しがないという事実が存在することで、このルールがキャッチされます。



  public override ProblemCollection Check(Member member) { var method = member as Method; if (method == null) { // This rule only applies to fields. // Return a null ProblemCollection so no violations are reported for this member. return null; } if (method.FullName.EndsWith(".Finalize")) { bool disposeFound = method.Instructions.Any(p => p.Value is Method && (p.Value as Method).FullName.EndsWith(".Dispose(System.Boolean)")); if (!disposeFound) { Resolution resolution = GetResolution(method); var problem = new Problem(resolution); Problems.Add(problem); } return Problems; } return Problems; }
      
      







例では、このようなファイナライザを実装しました。

  ~Samples() { Dispose(true); int i = 0; i++; int k = 56; Dispose(false); }
      
      





そのような指示のリストを受け取りましたが、まだDisposeへの呼び出しが1つだけであり、Falseパラメーターを使用していることを確認する方法を理解していません。



そのため、まだやるべきことがありますが、Dispose()呼び出しがないことを既に把握できます。



実装されたサービスは、サービスマネージャーに登録されます。



ここで、タスクはさらに興味深いものです。サービスクラスがあります(これは特別なインターフェイスを継承するクラスです)。

  public class UserService1 : IBaseService { } public class UserService2 : IBaseService { }
      
      





これらのサービスは、マネージャーに登録する必要があります。

  public class ServiceManager { public IDictionary<string, IBaseService> AllServices = new Dictionary<string, IBaseService>(); public void RegisterAllServices() { AllServices.Add("service1", new UserService1()); } }
      
      





UserService2サービスが登録されていない状況をキャッチする方法。

このようなルールを実装します(この場合、サービスとマネージャーが同じプロジェクトに属しているという事実を考慮に入れますが、別のときに試したことがない場合)。



少し異なる仮想メソッド「Check(TypeNode typeNode)」を実装し、まず目的のインターフェース(この場合は「.IBaseService」)から型が継承されていることを確認してから、「。ServiceManager」という名前の「DeclaringModule.Types」でサービスマネージャーを探します。 見つかった型で、目的のメソッド ".RegisterAllServices"を探し、最後に元の型を見つけます(または見つけません)。これはサービス "(.Contains(typeNode.FullName +"( "))"です。きれいではないことに同意します。登録がない場合、ルールは機能し、警告が表示されます。



  public override ProblemCollection Check(TypeNode typeNode) { if (typeNode.Interfaces.Any()) { InterfaceNode foundServiceInterface = typeNode.Interfaces.First(i => i.FullName.EndsWith(".IBaseService")); if (foundServiceInterface!=null) { bool foundUsage = false; TypeNode serviceManagerTypeNode = foundServiceInterface.DeclaringModule.Types.First(t => t.FullName.EndsWith(".ServiceManager")); if (serviceManagerTypeNode != null) { Member member = serviceManagerTypeNode.Members.First(t => t.FullName.EndsWith(".RegisterAllServices")); var method = member as Method; if (method != null) { foundUsage = method.Instructions.Any(opcode => opcode.Value != null && opcode.Value.ToString().Contains(typeNode.FullName + "(")); } } if (!foundUsage) { Resolution resolution = GetResolution(typeNode.FullName); var problem = new Problem(resolution); Problems.Add(problem); } } } return Problems; }
      
      







FxCopSdkライブラリのタイプをどのように研究したかを別に注意したいと思います。 悪い考えをしないでください、私はドキュメントを読んでいません(そして見さえしませんでした)。

OzCode (Visual Sudioの拡張)を利用しました。

たとえば、デバッガーにドロップして、このオブジェクトで必要なオブジェクトへのリンク(またはリンクのリスト)を見つける方法を探す代わりに、「このオブジェクトからServiceManagerに到達する方法を見つけて」検索を開始しました。



それでは、「タイプ」にはインターフェースのリストがあり、インターフェースにはDeclaringModuleがあり、タイプのリストがあります。そのうちの1つはServiceManagerです。

ozcodeが一般的な検索タスクのコードも生成する場合!



また、 公開機能を利用して、希望する形式でリスト全体を表示できるようにしました。





github.com/constructor-igor/UserDefinedCodeAnalysis githubにアップロードされたソース

Samplesフォルダーに、サンプルを使用してこれらのルールの分析を開始するsample.cmdファイルを配置します(もちろん、最初にすべてをコンパイルする必要があります)。

このファイルを開始すると、レポートファイルresults.xmlが生成されます。これには、ほぼ次の結果が含まれています。

  <Type Name="LogStaticFieldSamples" Kind="Class" Accessibility="Public" ExternallyVisible="True"> <Members> <Member Name="#m_logger2" Kind="Field" Static="False" Accessibility="Private" ExternallyVisible="False"> <Messages> <Message TypeName="EnforceStaticLogger" Category="MyRules" CheckId="CR1001" Status="Active" Created="2014-06-25 09:11:55Z" FixCategory="NonBreaking"> <Issue Certainty="101" Level="Warning">Field 'LogStaticFieldSamples.m_logger2' recommended be static, because type SamplesForCodeAnalysis.ILog.</Issue> </Message> </Messages> </Member> </Members> </Type>
      
      






All Articles