C#静的アナライザーを使用したハードコードとの戦い

この記事では、独自のコードアナライザーをどのように作成し、それを使用して.netコードベースを最も深刻な/頻繁に発生する妨害から削除する方法を説明します。 主なメッセージは、これを非常に簡単に行うことです。特定のバグに対処するためにアナライザを書くことを恐れないでください。 二次メッセージ-アナライザーを試して結果を報告します。 完全なガイドは作成しません。インターネット上にはかなりの数がありますが、ここでは、それが何であり、どのような問題に遭遇したかについて簡単に説明します。



問題



2016年12月に、転職して新しい会社に移りました。 新しいオフィスは、「部署の半分のプログラマーが社内のニーズに対応するために少しサービスを提供している」から「100人のIT企業が企業グループのニーズに合わせてERPシステムを開発し、外部ユーザー向けの製品を準備している」段階にありました。



当然、この移行の過程で、多くの膝パッドが「受け入れられた」ようになり、さまざまなベストプラクティスと真剣になりました。 特に、私が到着する数ヶ月前に会社にテスト環境の完全な階層が現れ、テスト部門は着実にテスト環境と戦闘環境の対応を求めて戦いました。



継承されたアーキテクチャの作成者はマイクロサービスアプローチを公言し、締め切りは厳しく、後輩の割合は大きかった。 テスト環境にデプロイすると、テスターは、テスト環境にデプロイされたマイクロサービスAが突然製品にマイクロサービスBを直接引き起こす可能性があるという事実に直面しました。 マイクロサービスBのURLは、マイクロサービスAのコード内に縫い付けられています。



作業の過程で、「ハードコードされたマイクロサービスリンク」の問題に加えて、他のコードの問題が明らかになりました。 コードレビューのチェックリストは拡大しており、宣誓は軽んじられていました。 必要なのは、すべての問題を特定し、将来そのような状況の発生を防ぐソリューションです。



静的アナライザー



新しいC#言語コンパイラであるRoslynは完全にオープンソースであり、プラグ可能な静的アナライザーのサポートが含まれています。 アナライザーは、エラー、警告、およびヒントを生成できますが、コンパイル中およびIDEでのリアルタイムの両方で機能します。



パーサーはAST (抽象構文ツリー)にアクセスでき、特定のタイプの要素(数値、文字列、メソッド呼び出しなど)をサブスクライブできます。 最も単純な形式では、次のようになります。



private static void AnalyzeLiteral(SyntaxNodeAnalysisContext context) { if (syntaxNode is LiteralExpressionSyntax literal) { var value = literal.Token.ValueText; if (value != "@" && value.Contains("@")) { context.ReportDiagnostic( Diagnostic.Create(Rule, context.Node.GetLocation(), value)); } }
      
      





(これは、ハードコードされた電子メールのアナライザーを作成する最初の試みです)。



ツリー内を上下に移動することができます。たとえば、コメントを文字列リテラルと区別する必要はありません。コンパイラパーサーがこれをすべて行います。 ただし、構文レベルでは不十分な場合があります。 例えば



  var guid = new Guid("...");
      
      





構文レベルでは、呼び出しているコンストラクタのタイプを理解できません。 ほとんどの場合、これはSystem.Guidです。 もしそうでなければ? 構文ツリーレベルでは、これは単に「Guid」に等しい識別子を持つクラスコンストラクターです。 より正確な診断を行うには、いわゆるセマンティックモデルを呼び出して、どのシンボルが構文ツリーの要素に対応するかについての情報を取得する必要があります。



 private static void AnalyzeCtor(SyntaxNodeAnalysisContext context) { if (context.Node.GetContainingClass().IsProbablyMigration()) { return; } if (context.Node is ObjectCreationExpressionSyntax createNode) { var type = context.SemanticModel.GetTypeInfo(createNode).Type as INamedTypeSymbol; var guidType = context.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.Guid).FullName); if (Equals(type, guidType)) { .. } } }
      
      





セマンティックモデルは、中間コンパイル結果です。 構文とは異なり、不完全である可能性があります(IDEのコンテキストで作業しているため、プログラマは分析済みのクラスをすぐに記述できます)。 セマンティックモデルのメソッドは多数あり、多くの場合nullを返します。



アナライザーは、通常のNuGetパッケージのようにプロジェクトに配置されます。 再コンパイル後、エラーと警告が表示され始めます。 私たちのオフィスでは、NuGetの内部パッケージを作成しました(通常、すべてをパッケージでビルドします)。これにより、推奨されるアナライザーの組み合わせが作成されます。 特定のオフィス機器をそこに追加することを計画しており、そこにパッケージを追加できます(たとえば、非常に便利なAsyncFixerを追加しました。すべての人にお勧めします)。



これがエディターでの表示です







そしてコンパイル結果で







作成したアナライザー



ハードURLアナライザー



私たちにとって最も重要なアナライザー。 http、https、ftpなどですべてのリテラルを愚かに検索します。 例外のリストは非常に多く、基本的にはさまざまな自動生成コード、SOAP属性、名前空間などが含まれます。



ハードコアVATレートアナライザー



まれですが、適切にコード内で問題が発生しました。 「VATを含む金額」は、「VATを含まない金額」から1.18を掛けて計算される場合があります。 これらは一度に2つの問題です。 まず、VATレートは変化する可能性があり、コード内のすべての場所を探すことは大きな問題です。 第二に、VAT率は実際には一定ではなく、特定の注文の属性です-VAT率が0%である場合があります(キャリアはしばしばそのような法人を持っています)、別の管轄に属している場合があり、最後にVAT率を変更すると、新しい注文ですが、古いものも同じです。

したがって、ハードコードされたVATはありません!



ここでの例外は、主に部分文字列(部分文字列)をカットする機能でした。 場合によっては、ハードに書かれた18を渡すことが理にかなっています。メソッドのパラメーターの名前でキャッチする必要があります。



ハードガイドアナライザー



多くの場合、コードからデータベースのIDを参照します(Guidを採用しています)。 これは悪いです。 私たちは特にステータステーブルに苦しんでおり、実際には単純にenumで行う必要があります(それらは取り除きますが、そうなっています)。 これまでのところ、中間ソリューションとして、すべての環境で同じである(つまり移行によって作成される)場合、および対応するエンティティのコードに直接配置されている場合(はい、Entity Framework 6を​​使用し、恥ずかしがり屋ではない場合)、コードでidを満たすことができることに同意しました。



私たちの計画に含まれるもの



-アナライザーのハードコードされたメール

-コントローラーメソッドの非同期書き込みを必要とするアナライザー



困難と奇妙な瞬間



共有ライブラリ



1つのアナライザー= 1つのアセンブリ= 1つのnugetパッケージの原則に基づいてアナライザーを分割するのは自然でした。 実際、私たちが書いたすべてのアナライザーは非常に意見が多く、それらを一度に1つずつ接続/切断する機能を提供することは論理的です。 アナライザーを作成する過程で、共通コードをアナライザーから共有ライブラリーに積極的に転送し始め、問題が発生しました:異なるバージョンのアナライザーがスタジオにロードされると、異なるバージョンの共有ライブラリーをロードできません。 最も簡単な解決策は、ビルド段階で各アナライザーを共通ライブラリーと単一のアセンブリにマージすることでした。 このために、MSBuildに便利なバインディング(ILRepack.MSBuild.Task)を持つILRepackを使用しました 。 残念ながら、バインディングは少し時代遅れですが、今のところ何の効果もないようです。



標準アナライザーの依存関係(Microsoft.CodeAnalysis。*)持ち歩く必要はありません。NuGetもそれらに依存しないでください。 Visual Studio自体が必要なライブラリを提供します。



Appveyorでビルドする



私はAppveyorでソースプロジェクトの継続的なビルドをセットアップするのに慣れています。 また、NuGetを収集して公開する方法も知っています。 しかし、アナライザーは特別な魔法です。 特に、パッケージ内のディレクトリ構造に関しては、すべてがそこで異なるため、標準のlibではなく、analyzersフォルダーに配置する必要があります。 私少し苦しんで決定しました-csprojで、このアセンブリがどのように登録されるかはNuget.exeを呼び出します。これはパッケージに含まれています。 それはasいですが、私には思えますが、より美しい解決策は見つかりませんでした。



単体テストの作成



アナライザーの単体テストは非常に便利で便利です。 アナライザーの単体テストに標準のMSラッパーを使用し、すぐに単体テストで誤検出をフレーム化しました。 いいね! ファジーな人間のTKをいじるよりもずっといい。



.netstandardへの切り替え



まだ.netstandartに切り替えることができません。 移動の試みは失敗しました、公式のテンプレートはまだポータブルクラスライブラリを生成します。 更新されたテンプレートを待って、それを試みます。 https://github.com/dotnet/roslyn/issues/18414



おわりに



私は自分のアプローチについて喜んでいます。 ソースはここにあります



また、アナライザーを分析するための優れたオープンソースC#プロジェクトをアドバイスしたり、自分でチェックしたり(オープンソースではない場合があります)、バグを報告したりしてくれたら嬉しいです。



できればResharper機能を複製しないで、他の便利なアナライザーへのリンクが本当に欲しいです。



All Articles