Skip to content

代码质量最佳实践

本文档介绍源生成器的代码质量最佳实践,包括文档注释、错误处理和代码格式化。


📋 文档信息

属性
难度中级
阅读时间15 分钟
前置知识C# 编程、代码规范

🎯 学习目标

学完本文档后,你将能够:

  • ✅ 编写完整的 XML 文档注释
  • ✅ 实施完善的错误处理
  • ✅ 生成格式良好的代码
  • ✅ 使用常量和配置管理

📝 完整的 XML 文档注释

为所有公共 API 添加文档注释

✅ 推荐:完整的文档注释

csharp
/// <summary>
/// 为标记了 [JsonSerializable] 的类生成 JSON 序列化代码
/// </summary>
/// <remarks>
/// 此生成器会为每个标记的类生成以下方法:
/// - ToJson(): 将对象序列化为 JSON 字符串
/// - FromJson(string): 从 JSON 字符串反序列化对象
/// </remarks>
[Generator]
public class JsonSerializerGenerator : IIncrementalGenerator
{
    /// <summary>
    /// 初始化生成器并注册源输出
    /// </summary>
    /// <param name="context">增量生成器初始化上下文</param>
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 实现...
    }
    
    /// <summary>
    /// 从语法上下文中提取类信息
    /// </summary>
    /// <param name="context">生成器特性语法上下文</param>
    /// <returns>包含类名和属性的类信息对象</returns>
    private ClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
    {
        // 实现...
    }
}

🔧 使用常量和配置

集中管理常量

✅ 推荐:使用常量类

csharp
/// <summary>
/// 生成器常量配置
/// </summary>
public static class GeneratorConstants
{
    /// <summary>
    /// 特性名称
    /// </summary>
    public const string AttributeName = "JsonSerializableAttribute";
    
    /// <summary>
    /// 生成文件后缀
    /// </summary>
    public const string GeneratedFileSuffix = ".g.cs";
    
    /// <summary>
    /// 生成文件的命名空间后缀
    /// </summary>
    public const string GeneratedNamespaceSuffix = ".Generated";
    
    /// <summary>
    /// 诊断 ID 前缀
    /// </summary>
    public const string DiagnosticIdPrefix = "JSG";
}

/// <summary>
/// 诊断描述符
/// </summary>
public static class Diagnostics
{
    public static readonly DiagnosticDescriptor InvalidClass = new(
        id: $"{GeneratorConstants.DiagnosticIdPrefix}001",
        title: "Invalid class for generation",
        messageFormat: "Class '{0}' must be partial to generate code",
        category: "Generator",
        DiagnosticSeverity.Error,
        isEnabledByDefault: true);
    
    public static readonly DiagnosticDescriptor NoProperties = new(
        id: $"{GeneratorConstants.DiagnosticIdPrefix}002",
        title: "No properties found",
        messageFormat: "Class '{0}' has no public properties",
        category: "Generator",
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true);
}

⚠️ 错误处理和诊断

完善的错误处理

✅ 推荐:使用 try-catch 和诊断

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            GeneratorConstants.AttributeName,
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                try
                {
                    return GetClassInfo(ctx);
                }
                catch (Exception ex)
                {
                    // 记录错误但不中断编译
                    System.Diagnostics.Debug.WriteLine($"Error processing class: {ex}");
                    return null;
                }
            })
        .Where(x => x != null);
    
    context.RegisterSourceOutput(classInfos, (spc, classInfo) =>
    {
        try
        {
            // 验证类信息
            if (!ValidateClassInfo(classInfo, spc))
                return;
            
            // 生成代码
            var code = GenerateCode(classInfo);
            spc.AddSource($"{classInfo.Name}{GeneratorConstants.GeneratedFileSuffix}", code);
        }
        catch (Exception ex)
        {
            // 报告错误诊断
            var diagnostic = Diagnostic.Create(
                new DiagnosticDescriptor(
                    $"{GeneratorConstants.DiagnosticIdPrefix}999",
                    "Code generation error",
                    "Error generating code for '{0}': {1}",
                    "Generator",
                    DiagnosticSeverity.Error,
                    isEnabledByDefault: true),
                Location.None,
                classInfo.Name,
                ex.Message);
            
            spc.ReportDiagnostic(diagnostic);
        }
    });
}

private bool ValidateClassInfo(ClassInfo classInfo, SourceProductionContext context)
{
    // 验证类名
    if (string.IsNullOrEmpty(classInfo.Name))
    {
        context.ReportDiagnostic(Diagnostic.Create(
            Diagnostics.InvalidClass,
            Location.None,
            "Unknown"));
        return false;
    }
    
    // 验证属性
    if (classInfo.Properties.Length == 0)
    {
        context.ReportDiagnostic(Diagnostic.Create(
            Diagnostics.NoProperties,
            Location.None,
            classInfo.Name));
        return false;
    }
    
    return true;
}

🎨 代码格式化

生成格式良好的代码

✅ 推荐:使用格式化辅助类

csharp
/// <summary>
/// 代码格式化辅助类
/// </summary>
public static class CodeFormatter
{
    /// <summary>
    /// 添加缩进的行
    /// </summary>
    public static void AppendIndentedLine(
        this StringBuilder sb,
        string line,
        int indentLevel)
    {
        sb.Append(new string(' ', indentLevel * 4));
        sb.AppendLine(line);
    }
    
    /// <summary>
    /// 添加空行
    /// </summary>
    public static void AppendEmptyLine(this StringBuilder sb)
    {
        sb.AppendLine();
    }
    
    /// <summary>
    /// 添加注释
    /// </summary>
    public static void AppendComment(
        this StringBuilder sb,
        string comment,
        int indentLevel)
    {
        sb.Append(new string(' ', indentLevel * 4));
        sb.AppendLine($"// {comment}");
    }
}

// 使用示例
private string GenerateCode(ClassInfo classInfo)
{
    var sb = new StringBuilder();
    
    sb.AppendLine("// <auto-generated />");
    sb.AppendEmptyLine();
    sb.AppendLine($"namespace {classInfo.Namespace}");
    sb.AppendLine("{");
    sb.AppendIndentedLine($"public partial class {classInfo.Name}", 1);
    sb.AppendIndentedLine("{", 1);
    sb.AppendComment("Generated properties", 2);
    
    foreach (var property in classInfo.Properties)
    {
        sb.AppendIndentedLine($"public {property.Type} {property.Name} {{ get; set; }}", 2);
    }
    
    sb.AppendIndentedLine("}", 1);
    sb.AppendLine("}");
    
    return sb.ToString();
}

📋 命名约定

生成器命名

✅ 推荐:清晰的命名

csharp
// ✅ 好的命名:清晰表达用途
[Generator]
public class JsonSerializerGenerator : IIncrementalGenerator { }

[Generator]
public class DependencyInjectionGenerator : IIncrementalGenerator { }

生成的文件命名

✅ 推荐:使用 .g.cs 后缀

csharp
// ✅ 好的命名:使用 .g.cs 后缀
spc.AddSource("MyClass.g.cs", code);
spc.AddSource("MyClass_Serializer.g.cs", code);

💡 关键要点

  1. 完整的 XML 文档注释 - 为所有公共 API 添加注释
  2. 使用常量和配置 - 集中管理配置
  3. 完善的错误处理 - 使用 try-catch 和诊断
  4. 代码格式化 - 生成格式良好的代码
  5. 清晰的命名约定 - 使用描述性的名称
  6. 验证输入 - 在生成代码前验证数据
  7. 提供有用的诊断 - 帮助用户理解错误

🔗 相关资源


📖 下一步

继续学习测试最佳实践 → 测试最佳实践


最后更新: 2025-01-21文档版本: 1.0

基于 MIT 许可发布