代码质量最佳实践
本文档介绍源生成器的代码质量最佳实践,包括文档注释、错误处理和代码格式化。
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 |
| 阅读时间 | 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);💡 关键要点
- 完整的 XML 文档注释 - 为所有公共 API 添加注释
- 使用常量和配置 - 集中管理配置
- 完善的错误处理 - 使用 try-catch 和诊断
- 代码格式化 - 生成格式良好的代码
- 清晰的命名约定 - 使用描述性的名称
- 验证输入 - 在生成代码前验证数据
- 提供有用的诊断 - 帮助用户理解错误
🔗 相关资源
📖 下一步
继续学习测试最佳实践 → 测试最佳实践
最后更新: 2025-01-21文档版本: 1.0