首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# Attribute 實戰:構建自動化代碼統計分析工具

     編輯:關於C語言

利用 C# 特性 將元數據嵌入程序集,配合反射實現零侵入式代碼統計分析:方法數統計、開發者貢獻度、Bug 標記追蹤、IL 代碼查看。本文提供完整架構設計與核心代碼。

為什麼選 Attribute?

  • 編譯時嵌入元數據:不像註釋被丟棄,Attribute 進入 Assembly Metadata,運行時可反射讀取
  • 類型安全:編譯器驗證參數類型,避免字符串解析錯誤
  • 零運行時開銷:僅在反射讀取時消耗性能,正常執行零影響
  • 工具鏈友好:IDE、靜態分析、CI/CD 皆可識別

核心 Attribute 定義

獨立類庫 CodeAnalytics.Attributes,供業務項目引用:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class CreatorAttribute : Attribute {
    public string Creator { get; }
    public string CreateDate { get; }
    public CreatorAttribute(string creator, string createDate) {
        Creator = creator;
        CreateDate = createDate;
    }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class ReviewerAttribute : Attribute {
    public string Reviewer { get; }
    public string ReviewDate { get; }
    public ReviewerAttribute(string reviewer, string reviewDate) {
        Reviewer = reviewer;
        ReviewDate = reviewDate;
    }
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class BugAttribute : Attribute {
    public bool HasBug { get; }
    public string BugDate { get; }
    public BugAttribute(bool hasBug, string bugDate) {
        HasBug = hasBug;
        BugDate = bugDate;
    }
}

業務代碼標註示例

[Creator("join", "2024-01-15")]
public class OrderService {
    [Creator("join", "2024-01-15")]
    [Reviewer("alice", "2024-01-16")]
    [Bug(false, "")]
    public void CreateOrder(OrderDto dto) { /* ... */ }

    [Creator("bob", "2024-01-18")]
    [Reviewer("alice", "2024-01-19")]
    [Reviewer("charlie", "2024-01-20")]
    [Bug(true, "2024-01-21")] // 曾有 Bug
    public void CancelOrder(int orderId) { /* ... */ }
}

統計分析引擎核心

public sealed class AssemblyAnalyzer {
    public AnalysisReport Analyze(string assemblyPath) {
        var asm = Assembly.LoadFrom(assemblyPath);
        var report = new AnalysisReport { AssemblyName = asm.GetName().Name };

        foreach (var type in asm.GetTypes()) {
            var typeCreator = type.GetCustomAttribute();
            foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) {
                if (method.IsSpecialName) continue; // 跳過屬性 get/set、構造函數等

                var creator = method.GetCustomAttribute() ?? typeCreator;
                var reviewers = method.GetCustomAttributes().ToList();
                var bug = method.GetCustomAttribute();

                report.Methods.Add(new MethodStat {
                    FullName = $"{type.FullName}.{method.Name}",
                    Creator = creator?.Creator ?? "Unknown",
                    CreateDate = creator?.CreateDate,
                    Reviewers = reviewers.Select(r => r.Reviewer).ToList(),
                    HasBug = bug?.HasBug ?? false,
                    BugDate = bug?.BugDate,
                    ILCode = GetILCode(method) // 輔助功能
                });
            }
        }
        return report;
    }

    private string GetILCode(MethodInfo method) {
        var body = method.GetMethodBody();
        if (body == null) return "N/A (Abstract/Extern)";
        var il = body.GetILAsByteArray();
        return string.Join(" ", il.Select(b => b.ToString("X2")));
    }
}

public class AnalysisReport {
    public string AssemblyName { get; set; }
    public List Methods { get; } = new List();
    public int TotalMethods => Methods.Count;
    public int BugMethods => Methods.Count(m => m.HasBug);
    public Dictionary CreatorStats => Methods
        .GroupBy(m => m.Creator)
        .ToDictionary(g => g.Key, g => g.Count());
    public Dictionary ReviewerStats => Methods
        .SelectMany(m => m.Reviewers)
        .GroupBy(r => r)
        .ToDictionary(g => g.Key, g => g.Count());
}

public class MethodStat {
    public string FullName { get; set; }
    public string Creator { get; set; }
    public string CreateDate { get; set; }
    public List Reviewers { get; set; } = new List();
    public bool HasBug { get; set; }
    public string BugDate { get; set; }
    public string ILCode { get; set; }
}

報表輸出示例

方法創建者創建日期審核者有 BugBug 日期IL 字節碼
OrderService.CreateOrderjoin2024-01-15alice否-02 28 0A ...
OrderService.CancelOrderbob2024-01-18alice, charlie是2024-01-2116 72 01 00 00 0A ...

關鍵設計決策

  1. 統計 IL 行數而非源代碼行:針對編譯後程序集,源代碼需 Roslyn 解析,IL 直接獲取,且反映真實執行邏輯複雜度
  2. Creator 可繼承:方法無標註時回溯類級別,減少重複標註
  3. Reviewer 允許多重:支持多輪 Code Review 記錄
  4. Bug 標記不可重複:一個方法要么有 Bug 要么無,避免歧義

局限與擴展

  • 無法統計私有邏輯行數:IL 包含編譯器生成代碼,非原始邏輯行數
  • 擴展:集成 Roslyn 可分析源代碼級指標(圈複雜度、認知複雜度、重複代碼)
  • 擴展:CI 集成 作為 Build 任務,生成 HTML 報表,阻斷高 Bug 密度代碼合入
  • 擴展:IDE 插件 在編輯器內實時顯示方法擁有者、審核狀態

結語

Attribute + 反射 是構建輕量級代碼治理工具的黃金組合。幾十行核心代碼,即可賦能團隊掌握代碼所有權、審核流轉、缺陷分布。不要讓元數據沉睡在 DLL 裡——讓它們為工程效能發聲。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved