はじめに
前回・前々回の記事では、自作のC#静的解析ツール『CSharp_IngeniousAnalyzer』の開発背景や導入方法、搭載ルールの詳細についてお話しさせていただきました。おかげさまで、今も多くの方にダウンロードいただいており、心より感謝申し上げます!
前回までの記事:
- 業務の「あるといいな」を実現したC#アナライザーをNuGetで公開したらDL数が伸びたのだけど
- C#アナライザーをVisual Studioから導入する方法:NuGetパッケージマネージャー活用編
- C#の不毛なコードレビューをゼロに!CSharp_IngeniousAnalyzer 全11ルール徹底解説
今回、v3.1.0として新たに2つのルール『EXC001(空のcatchブロック検知)』『STR003(冗長なToString()呼び出し検知)』を追加しましたので、その内容と、あわせて実施した不具合修正について紹介させてください。
今回追加した2つの新ルール
🔹 EXC001:空のcatchブロックを検知する
- タイトル: 例外処理の明確化による保守性向上
- メッセージ: 「'{0}' のcatchブロックが空です。処理を追加するか、意図的な場合はその理由をコメントで記述してください。」
解説: 中身が空のcatchブロックは、例外を握りつぶして問題を見えなくしてしまう典型的なアンチパターンです。とはいえ、業務コードの中には「このケースは意図的に無視したい」という正当な理由で空にしているcatchも存在します。そこで本ルールでは、ブロック内が完全に空の場合のみを検知対象とし、コメントが1行でも存在すれば「意図的な握りつぶし」とみなして警告を出さないようにしています(開き括弧と同じ行のコメント/閉じ括弧の手前のコメント、どちらの書き方にも対応しています)。
検知される例/されない例:
// 🔴 検知される:完全に空
try
{
DoSomething();
}
catch (Exception ex)
{
}
// 🟢 検知されない:コメントがあれば「意図的」とみなす
try
{
DoSomething();
}
catch (Exception ex)
{
// 何もしない
}
Fixによる修正例: 本ルールはあえて「削除」や「自動でthrowを挿入する」といった修正は行わず、TODOコメントを挿入するだけのFixにしています。
なお、既知の制限として、ハンドラの中身をコメントアウトしただけのケース(// LogError(ex);のみ)や、セミコロン1つだけの空文(;)は、仕様上「意図的」もしくは「空ではない」と判定します。
🔹 STR003:冗長なToString()呼び出しを削除する
- タイトル: 冗長なToString()呼び出しの削除によるコード最適化
- メッセージ: 「'{0}' は既にstring型です。冗長な '.ToString()' 呼び出しを削除してください。」
解説: 既にstring型である変数や式に対して、習慣的に.ToString()を呼んでしまっているコードをよく見かけます。これは単に冗長なだけでなく、僅かながら無駄なメソッド呼び出しコストも発生します。本ルールでは、セマンティックモデルを使って呼び出し元の静的な型がstringであることを確定した上で警告します。
ポイントは、null安全性への配慮です。通常のs.ToString()という書き方は、sがnullだとNullReferenceExceptionを投げてしまうため、コンパイラのNull許容参照型解析によって「このタイミングでは絶対にnullではない」と証明できる場合に限定して警告しています。一方で、s?.ToString()のようなnull条件演算子を使ったケースは、nullならそのままnullが返るだけでセマンティクスが変わらないため、フロー解析の証明なしに警告対象としています。
検知される例/されない例:
// 🔴 検知される:非null許容が確定しているstring型への呼び出し
string s1 = "abc";
var r1 = s1.ToString();
// 🔴 検知される:null条件演算子はnull安全なので確定していなくても対象
string? s2 = GetNullableString();
var r2 = s2?.ToString();
// 🟢 検知されない:nullになり得ることが確定できない通常呼び出し(誤検知回避)
string? s3 = GetNullableString();
var r3 = s3.ToString();
Fixの実装では、単純に.ToString()部分を取り除くだけでなく、WithTriviaFromという拡張メソッドを使うことで、コメントや改行位置(Trivia)を元の呼び出し全体からそのまま引き継ぐようにしています。以前は手作業でTriviaを移植するロジックを書いていて地味に苦労していたのですが、この方法にしてからかなりシンプルに書けるようになりました。
あわせて実施した不具合修正
新ルールの追加と同時に、以下の不具合修正もv3.1.0に含めています。
-
GenerateDocumentationFileが未設定のプロジェクトで、COMM001/COMM002(関数ヘッダー関連のルール)が正しく動作しない不具合を修正 -
STR002/STR003のFixが、一部のケースで正しく動作しない不具合を修正
地味な修正ではありますが、実際に導入いただいている環境で気持ちよく使っていただくためには欠かせない部分だと考えています。
おわりに
今回追加した2つのルールも、これまでと同じく「Fixによる快適な修正体験」と「意図的なコードを誤検知しない配慮」を大切にしながら実装しました。
もし実際に試してみて、この検知パターンも拾ってほしい、このケースは誤検知ではないか、新しいルール『XXX001』を追加してほしいといったご意見・アイデアがありましたら、ぜひ本記事のコメント欄や、GitHubのIssues/Discussions、NuGetページから気軽にお知らせください!
みなさんと一緒に、より便利で快適なC#開発環境を作っていければ幸いです。
最後までお読みいただき、ありがとうございました!





