Now in REALITY Tech #44 C#の静的解析ツール・RoslynAnalyzerの導入をしました
REALITYでサーバ、インフラ、Unityなどのエンジニアをしている tk_3118 です。
今回はREALITYのUnityプロジェクトに、C#の静的解析ツール(linter)の導入を行ったのでそのお話しを少ししたいと思います。
開発をしているとプロジェクトによって独自のルールを設けるなどして、それらは大抵のケースにおいてコードレビュー時に発覚したり、さらに開発が長期化したりするとルールなども増え、チームメンバーがルールを把握しながら実装をするのが困難になったり、チームメンバーの増加などにより、ルールや歴史的経緯の把握がチームメンバー内での浸透が薄くなっていったりなど、ルールを設けて守っていくことには課題があります。
特に開発をスケールしたい場合などにおいて、新規メンバーがこういった内容を把握するのは困難であることがほとんどでしょう。
今回はこういった状況を解決したいと考えその取り組みの1つを紹介します。
こういったものはソースコードの静的解析を行うことで、ある程度解決できるケースがあると考えています。
具体的なイメージを固める為1つ具体的な例を紹介します。
例えばREALITYでは、UnityEngine.Debug.Log ではなく 独自の Logger を使用するルールなどがあります。
こういったものを、Unityエディタ,コードエディタと連携させることで、機械的に検知及び警告を出力出来ます。
以下はコードエディタ上での検知例になります。
このように機械的なチェックが行われるようになった結果、機能実装に対してリアルタイムで注意喚起を行うことができるようになり、ルールを機械的に浸透することが出来るようになり、開発者がルールを守るために使っている頭のリソースが機能実装の部分に割くことが出来るようになる他、また特定のレビュアーでないと歴史的経緯を指摘できない問題であったり、本来行いたい機能実装の本筋部分に対するコードレビューに注力することができるようになります。
これが実現できたらかなり開発がやりやすくなるとは思いませんか?
それでは具体的な手法を見て行きましょう。
静的解析ツールとは...
静的解析ツールって何でしょうか?という話しなのですが、要はlinterです。
とは言ってもこの説明では容量を得ないと思うので、引用ですがこんな感じです。
ということで、これはかなり便利ではないでしょうか?
他の言語ではjsだとeslint、pythonだとflake8など、golangだとgolangci-lintなどのようなツールになります。
これらを使用したことがある方はその便利さが伝わることでしょう。
Unity(C#)で使える静的解析ツールとしては、RoslynAnalyzerというものがあります。
今回はこちらのRoslynAnalyzerを導入していきます。
RoslynAnalyzerとは...
RoslynAnalyzerとは、C#のコンパイラ実装の1つであるRoslynに含まれるコード解析APIで、こちらの機能を使用して抽象構文木を取り出すことができるため、その結果の構文木を用いて静的解析処理を走らせることが出来ます。
これらを使用し開発者が独自のルールのためのAnalyzerを自作したり、検知したものを自由にwarningやerrorとしてユーザに出力することができるようになります。
UnityとRoslynAnalyzerに関して
UnityでRoslynAnalyzerがサポートされたのはUnityエディタ2020.2からになるのですがこの段階では結構実験的なサポートと言える状況でした。
というのもUnityエディタ側では解析が機能する(ConsoleLogに出力される)のですが、コードエディタ側では機能しないという問題などがありました(csprojにAnalyzerの設定が適用されていない為)
つまり、Unityでの静的解析対応としてはUnityエディタとコードエディタの両方で動作していることが望まれるわけです。
一時期は https://github.com/Cysharp/CsprojModifier のようなUnityのプラグインでcsprojの生成をハックしてAnalyzerの設定を差し込むということもありましたが、Unityがコードエディタプラグイン側でもAnalyzerのサポートを始め、様々なバグや不具合を経て最終的には以下のバージョン以降で安定していると考えています。
JetBrains Rider Editor v3.0.9
Code Editor Package for Visual Studio Code v1.2.4
Code Editor Package for Visual Studio v2.0.11
Unityエディタは2020.3.6f1以降が対象ですが、2020の最新LTS以降にしておけば基本的に動作します。
これらの状況に関しては、こちらが参考になりました
REALITY Unityチームでは、Jetbrains Riderを主にコードエディターとして使用していて、一部ファイル編集などでVisual Studio Code を使用することから「JetBrains Rider Editor」、「Code Editor Package for Visual Studio Code」の対応を行いました。
注意点としては、各コードエディタプラグイン(及びバージョン)によって生成するcsprojに差があるのでここに関しては注意が必要です。
また使用していると、Analyzerが動作しないということがあるのですが、
その場合は基本的にcsprojの中身を確認します。Analyzerプロパティブロックの確認とその中のAnalyzerの実体へのパスが正確かどうかを主に確認します。
よくわからない場合はとりあえずcsprojを再生成してみるというのも良いでしょう。
RoslynAnalyzerの導入と行ったこと
さて、ここまででAnalyzerを使う土台の準備を進めてきたのですが、具体的にAnalyzer自体を導入する部分に関してとそれに対して行ったことをお話しします。
RoslynAnalyzerの良い点として比較的容易に開発者が独自のAnalyzerを作成することが可能で、その為用途によって別れた様々な静的解析がOSSなどで公開されていたりします。
ほぼ標準と言える https://github.com/dotnet/roslyn-analyzers でも複数のAnalyzerが実装されていたりします。
その為、プロジェクトにとって必要なAnalyzerを1つまたは複数導入することになります。REALITYでも複数のAnalzerを導入しています。
それでは具体的な導入をしてみましょう。
RoslynAnalyzerの導入としては主に以下の手順になります。
Analyzer用sln/csprojの作成及び実装(OSSのものを使う場合は省略)
Analyzerのプロジェクトをビルドしてマネージドdllの生成
Unity側プロジェクトに生成したマネージドdllをインポート
マネージドdllのアセット設定から対象プラットフォームを全てオフに変更
AssetLabelsにRoslynAnalyzerを設定
とこんな感じの手順で導入することが出来ます。色々設定したい場合は別途操作が必要ですがベースはこのような感じになることがほとんどでしょう。
3~5で何度かスクリプトコンパイルが走り多少待つのと、これを他のプロジェクトでも導入する場合同じようなことをしなくてはいけなかったり、Analyzerを取り除く場合もディレクトリなどがまとまっていない場合削除が手間だったりします。
ちょっと煩雑ですね...
ということでREALITYではUPM(Unity Package Manager)経由で導入するようにしています。
AnalyzerのUPM対応に関しては、調べれば出てくるのでここでは詳しく説明しませんが、NuGetで公開されているものなどは、https://github.com/xoofx/UnityNuGet で提供されているUPM Registryを使用することで簡単に導入することが出来ます。(何個かPRを送りました)
REALITYではNuGetで公開されているものとしては以下のAnalyzerを導入しており、その設定は下記になります。
BannedApiAnalyzers
Microsoft.VisualStudio.Threading.Analyzers
UniRxAnalyzer
"dependencies": {
"org.nuget.microsoft.visualstudio.threading.analyzers": "17.1.46",
"org.nuget.unirxanalyzer": "1.0.4-1",
"org.nuget.microsoft.codeanalysis.bannedapianalyzers": "3.3.3",
(今回は各々のAnalyzerの機能に関して言及しません、気になる方は調べてみると記事がいくつか出てきます)
UPM管理になったことで導入も簡単になり、Analyzerを取り除きたい場合もかなり容易に行えるようになったかと思います。
Analyzerは導入できたのですが使用していると、
細かい設定の調整を行ったりしたくなる場合があるでしょう、
1つのユースケースとしてseverity(重大度)の変更に関して話します、
Analyzerが出力するseverityは開発者が設定したもので出力される為、チーム内ではエラーにしたいという場面では少し困ってしまいます。
RoslynAnalyzerではこういった設定をユーザが一部上書き・設定することが可能になっていて、これらの設定には基本的にRulesetファイル及びeditorconfigまたはDotSettingsを用いることが一般的です。
各使い分けは以下のような感じが良いかなと考えています。
Rulesetファイル: (Unityエディタ及びコードエディタ両方に対して行いたい設定)
editorconfig/DotSettings: (コードエディタに対してのみの設定)
こちらの例では「VSTHRD200」を「error」にする設定をしています。
Rulesetファイル
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Default" Description="default severity ruleset" ToolsVersion="10.0">
<Rules AnalyzerId="Microsoft.VisualStudio.Threading.Analyzers" RuleNamespace="Microsoft.VisualStudio.Threading.Analyzers">
<Rule Id="VSTHRD200" Action="Error" />
</Rules>
</RuleSet>
editorconfig
[*.cs]
dotnet_diagnostic.VSTHRD200.severity = error
このように設定をすることでseverityを変更することが出来ました。
最後に特定の部分に対しての静的解析を検知するということに関して話します。
これはasmdef(Assembly Difinition)を分けることによって実現できるのですが、運用プロジェクトだとこちらの対応のハードルがかなり高い場合が多いです。
長期間運用しているとあらゆるフェーズがありメンバーも増減したりしていくなかで、コード同士の相互参照を起こしてしまったり綺麗なモジュール階層構造になっていないことがほとんどでしょう。
そうなってしまうと、Analyzerが全てのコードに対して静的解析をおこなってしまい本来検知してほしくないサードパーティアセットのコードなどでも検知されてしまいそれがノイズになり本来の指摘が埋もれてしまうといったことが起こります。
こういったケースにおいては、ディレクトリ毎にseverityを変更することで検知対象を絞ることが出来ます。
以下はeditorconfigの例ですが、この設定では「ThirdParty」下では「RS0030」のseverityをnoneにして、「Target」下では「RS0030」のseverityをerrorとしています。
editorconfig
[Assets/ThridParty/**.cs]
dotnet_diagnostic.RS0030.severity = none
[Assets/Target/**.cs]
dotnet_diagnostic.RS0030.severity = error
こうすることで無事特定の部分に対してAnalyzerの検知を絞ることが出来ました。
ここでは説明しないのですが、コード単位でAnalyzer検知を抑制する方法などもあります。
今回は導入に関しての話しとそれに対して行ったことの一部をお話しさせていただきました。
さらなる細かい話しはまたどこかで...!
ここまで読んだ方お疲れ様でした&ありがとうございました。
皆さんのRoslynAnalyzer導入の一助になりますと幸いです。
それでは...!
おまけ
今回は一旦の試験的な導入ということもあり行わなかったことではあるのですが、RoslynAnalyzerは独自のAnalyzerを自作することが容易になるように設計されており、プロジェクト独自のルールなどに特化したAnalyzerを開発することもできます。
このあたり、簡単なサンプル実装に関しては、neueccさんのブログ で書かれていたり
より具体的な実装の参考としては、microsoft/dotnet-analyzer などの実装が参考になります。
興味ある方はその辺りを見ていただくのが良いかもしれません。
更なる発展としてこれらの検知をより強固にしたい場合などは、
各々CI/CDに載せてコミットをトリガーにして自動化したり、git-hook経由で走らせるなどをすると良いでしょう。
RoslynAnalyzerは、割と新しい機能ということもあり現状まだ導入例などもあまりなく、挙動が怪しいケースも報告されているのでpreviewくらいかなという感じではいますが、導入にあたって最低限必要なものは揃っている認識です。
皆さんもUnityでの静的解析行ってみてはいかがでしょうか...?