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异步与空值问题检查
  5. 05CI 和提交前质量检查

适用场景

  1. 01统一 .NET 项目代码风格
  2. 02在提交前检查格式
  3. 03逐步启用分析器
  4. 04排查异步与可空性问题

安装

复制下面的命令,在终端中运行。

$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 测试技能

深入了解:使用 mcp__documentation__fetch_docs,并将 technology 设为 dotnet,获取全面的文档。

工具概览

工具重点安装方式
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. 可空引用类型
// 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 警告隐藏实际问题修复,或说明原因后抑制
未启用可空引用类型NullReferenceException启用 <Nullable>enable</Nullable>
#pragma warning disable隐藏问题修复,或针对具体问题进行抑制
可变 DTO产生意外变更使用记录类型或仅初始化属性

快速故障排除

问题可能原因解决方案
分析器未运行未安装程序包添加到 .csproj
警告过多首次启用抑制警告并逐步修复
构建时格式发生变化设置不一致提交 .editorconfig
到处都有可空性警告旧代码按项目逐步启用
StyleCop 规则冲突约定不同在 .editorconfig 中配置

相关技能

评论

支持 Markdown