mcpField
D
dotnet-quality

dotnet-quality

github.com/claude-dev-suiteテスト・品質4639 回インストール2 日前に更新
リポジトリ

dotnet-quality について

.NET 開発者向けのコード品質リファレンスです。フォーマット、Roslyn アナライザー、スタイルツールの設定と利用方法をまとめています。

主な機能

  1. 01dotnet format による整形と修正
  2. 02Roslyn アナライザーの設定
  3. 03一般的な警告への対処指針
  4. 04非同期処理と null 許容性の確認
  5. 05CI とコミット前の品質チェック

活用シーン

  1. 01.NET プロジェクトのスタイル統一
  2. 02コミット前のフォーマット確認
  3. 03アナライザーの段階的な有効化
  4. 04非同期処理と null 許容性の問題診断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill dotnet-quality

インストール後、Claude Code で/dotnet-qualityを入力して読み込まれたか確認します。

詳細

.NET 品質 - クイックリファレンス

このスキルを使用しない場合

  • SonarQube のセットアップ - sonarqube スキルを使用してください
  • セキュリティスキャン - dotnet-security スキルを使用してください
  • テスト - .NET のテストスキルを使用してください

詳しい情報:包括的なドキュメントについては、テクノロジーに dotnet を指定して mcp__documentation__fetch_docs を使用してください。

ツールの概要

ツール対象インストール
dotnet formatフォーマット+アナライザー組み込み
Roslyn Analyzersコード分析NuGet
StyleCop.AnalyzersスタイルルールNuGet
RoslynatorリファクタリングNuGet
SonarAnalyzer.CSharpSonarQube ルールNuGet

Roslyn Analyzers のセットアップ

アナライザーのインストール

<Project>
  <PropertyGroup>
    <AnalysisLevel>latest-all</AnalysisLevel>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
    <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Roslynator.Analyzers" Version="4.10.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
.editorconfig
# .editorconfig
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.cs]
# Organize usings
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false

# this. qualification
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning

# Language keywords vs BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning

# var preferences
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# Expression-bodied members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion

# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning

# Null checking
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:warning
dotnet_style_coalesce_expression = true:warning
dotnet_style_null_propagation = true:warning

# Code style
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion

# Naming conventions
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_rule.private_field_should_be_camel_case_with_underscore.severity = warning
dotnet_naming_rule.private_field_should_be_camel_case_with_underscore.symbols = private_field
dotnet_naming_rule.private_field_should_be_camel_case_with_underscore.style = camel_case_underscore

dotnet_naming_symbols.private_field.applicable_kinds = field
dotnet_naming_symbols.private_field.applicable_accessibilities = private
dotnet_naming_style.camel_case_underscore.required_prefix = _
dotnet_naming_style.camel_case_underscore.capitalization = camel_case

# Analyzer severity
dotnet_diagnostic.CA1062.severity = warning  # Validate arguments
dotnet_diagnostic.CA1307.severity = warning  # Specify StringComparison
dotnet_diagnostic.CA1310.severity = warning  # Specify StringComparison for correctness
dotnet_diagnostic.CA2007.severity = none     # ConfigureAwait (not needed in ASP.NET Core)
dotnet_diagnostic.IDE0058.severity = none    # Expression value never used

# StyleCop
dotnet_diagnostic.SA1101.severity = none     # Prefix local calls with this
dotnet_diagnostic.SA1309.severity = none     # Field names must not begin with underscore
dotnet_diagnostic.SA1600.severity = none     # Elements should be documented
dotnet_diagnostic.SA1633.severity = none     # File should have header

dotnet format

コマンド
# Check formatting
dotnet format --verify-no-changes

# Fix formatting
dotnet format

# Specific project
dotnet format ./src/MyProject

# Analyzers only
dotnet format analyzers

# Style only
dotnet format style

# Whitespace only
dotnet format whitespace

# With severity
dotnet format --severity warn

よくあるアナライザー警告

CA1062 - 引数の検証
// BAD - No null check
public void Process(string input)
{
    Console.WriteLine(input.Length);  // CA1062
}

// GOOD - Null check
public void Process(string input)
{
    ArgumentNullException.ThrowIfNull(input);
    Console.WriteLine(input.Length);
}

// GOOD - Nullable reference type
public void Process(string? input)
{
    if (input is null) return;
    Console.WriteLine(input.Length);
}
CA1307/CA1310 - StringComparison
// BAD - Culture-dependent
if (name.Equals("admin"))  // CA1307

// GOOD - Explicit comparison
if (name.Equals("admin", StringComparison.OrdinalIgnoreCase))

// GOOD - For user-facing
if (name.Equals(otherName, StringComparison.CurrentCultureIgnoreCase))
CA2000 - オブジェクトの破棄
// BAD - Not disposed
public void Process()
{
    var stream = new FileStream("file.txt", FileMode.Open);  // CA2000
    // forgot to dispose
}

// GOOD - Using statement
public void Process()
{
    using var stream = new FileStream("file.txt", FileMode.Open);
    // automatically disposed
}
IDE0090 - ターゲット型の new を使用
// Before
List<string> items = new List<string>();

// After
List<string> items = new();

// Or with var
var items = new List<string>();

よくあるコードスメルと修正方法

1. 神クラス
// BAD - Does too much
public class OrderProcessor
{
    public void CreateOrder() { ... }
    public void SendEmail() { ... }
    public void GeneratePdf() { ... }
    public void CalculateTax() { ... }
}

// GOOD - Single responsibility
public class OrderService
{
    private readonly IEmailService _emailService;
    private readonly IPdfGenerator _pdfGenerator;
    private readonly ITaxCalculator _taxCalculator;

    public OrderService(
        IEmailService emailService,
        IPdfGenerator pdfGenerator,
        ITaxCalculator taxCalculator)
    {
        _emailService = emailService;
        _pdfGenerator = pdfGenerator;
        _taxCalculator = taxCalculator;
    }

    public async Task<Order> CreateOrderAsync(OrderRequest request)
    {
        var order = BuildOrder(request);
        order.Tax = _taxCalculator.Calculate(order);
        return order;
    }
}
2. Async/Await の落とし穴
// BAD - Blocking async
public void Process()
{
    var result = GetDataAsync().Result;  // Deadlock risk!
}

// BAD - Async void (except event handlers)
public async void ProcessAsync()  // Can't be awaited, exceptions lost
{
    await DoWorkAsync();
}

// GOOD - Async all the way
public async Task ProcessAsync()
{
    var result = await GetDataAsync();
}

// GOOD - When truly fire-and-forget
public void StartBackgroundWork()
{
    _ = Task.Run(async () =>
    {
        try { await DoWorkAsync(); }
        catch (Exception ex) { _logger.LogError(ex, "Background work failed"); }
    });
}
3. Null 許容参照型
// Enable in .csproj
// <Nullable>enable</Nullable>

// BAD - Ignoring nullability
public string GetName(User user)
{
    return user.Name;  // Warning if Name is string?
}

// GOOD - Handle null
public string GetName(User user)
{
    return user.Name ?? "Unknown";
}

// GOOD - Return nullable
public string? GetName(User? user)
{
    return user?.Name;
}
4. DTO にレコード型を使用
// BAD - Mutable class with boilerplate
public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    // Equals, GetHashCode, ToString...
}

// GOOD - Immutable record
public record UserDto(int Id, string Name, string Email);

// With validation
public record UserDto
{
    public int Id { get; init; }
    public string Name { get; init; }
    public string Email { get; init; }

    public UserDto(int id, string name, string email)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(name);
        ArgumentException.ThrowIfNullOrWhiteSpace(email);

        Id = id;
        Name = name;
        Email = email;
    }
}
5. パターンマッチング
// BAD - Type checking with cast
if (shape is Circle)
{
    var circle = (Circle)shape;
    return circle.Radius * circle.Radius * Math.PI;
}

// GOOD - Pattern matching
if (shape is Circle circle)
{
    return circle.Radius * circle.Radius * Math.PI;
}

// GOOD - Switch expression
return shape switch
{
    Circle c => c.Radius * c.Radius * Math.PI,
    Rectangle r => r.Width * r.Height,
    _ => throw new ArgumentException("Unknown shape")
};

プレコミットのセットアップ

.pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dotnet-format
        name: dotnet format
        entry: dotnet format --verify-no-changes
        language: system
        types: [c#]
        pass_filenames: false

      - id: dotnet-build
        name: dotnet build
        entry: dotnet build --no-restore -warnaserror
        language: system
        types: [c#]
        pass_filenames: false

品質指標の目標値

指標目標値ツール
循環的複雑度< 10Roslynator
メソッドの行数< 50StyleCop
パラメーター数< 5CA1026
テストカバレッジ> 80%coverlet
保守性インデックス> 60VS Metrics

CI/CD との統合

GitHub Actions
name: Quality
on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore

      - name: Format check
        run: dotnet format --verify-no-changes

      - name: Build
        run: dotnet build --no-restore -warnaserror

      - name: Test
        run: dotnet test --no-build --collect:"XPlat Code Coverage"

      - name: Upload coverage
        uses: codecov/codecov-action@v4

VS Code / Rider の設定

// .vscode/settings.json
{
  "omnisharp.enableEditorConfigSupport": true,
  "omnisharp.enableRoslynAnalyzers": true,
  "[csharp]": {
    "editor.defaultFormatter": "ms-dotnettools.csharp",
    "editor.formatOnSave": true
  }
}

アンチパターン

アンチパターン問題点推奨される方法
.Result / .Wait()デッドロックのリスクasync/await を使用
async void例外が失われるasync Task を使用
CA 警告を無視する実際の問題が隠れる修正するか、理由を添えて抑制する
Null 許容参照型を使用しないNullReferenceException<Nullable>enable</Nullable> を有効にする
#pragma warning disable問題が隠れる修正するか、対象を絞って抑制する
ミュータブルな DTO予期しない変更レコードまたは init 専用プロパティを使用

よくある問題の解決方法

問題考えられる原因解決策
アナライザーが実行されないパッケージがインストールされていない.csproj に追加する
警告が多すぎる初回の有効化抑制し、段階的に修正する
ビルド時にフォーマットが変更される設定が異なる.editorconfig をコミットする
Null 許容に関する警告が至るところで表示されるレガシーコードプロジェクトごとに段階的に有効にする
StyleCop と競合する規約が異なる.editorconfig で設定する

関連スキル

コメント