Case #1: 以 Trace.Assert 強化 API 前置條件(避免 XML 參數為 null 或結構不一致)
Problem Statement(問題陳述)
業務場景:線上測驗系統在批改考卷時計算每題得分。後端服務接收兩個 XML:題目定義(quiz_question)與學生作答(paper_question)。在高併發與多來源資料整合情境中,常出現空參考、項目數不一致、節點遺失,導致系統計分錯誤或崩潰,影響批改準確性與使用者體驗。
技術挑戰:在不影響核心邏輯的前提下,快速暴露並定位前置資料錯誤;確保計分前 API 參數契約(not null、子節點數一致)被嚴格檢查。
影響範圍:造成 NullReferenceException、分數異常、批改服務中斷;影響整份考卷與整批考生的評分。
複雜度評級:低
Root Cause Analysis(根因分析)
直接原因:
- 缺乏輸入資料前置檢查:未檢查 null 與子節點數一致性。
- UI/上游服務假設成立:假設前端/上游已保證資料正確,未在後端防禦。
- 異常訊息不一致:出錯時缺乏明確錯誤位置與上下文。
深層原因:
- 架構層面:API 契約未沉澱為明確的前置條件;缺少邊界保護層。
- 技術層面:未建立 Debug/Release 下的斷言與追蹤策略。
- 流程層面:缺乏 Code Review 檢查「契約檢查」是否到位。
Solution Design(解決方案設計)
解決策略:在計分入口加上 Trace.Assert 作為前置條件檢查,於 Debug/測試環境主動中止並提供上下文,促使問題快速暴露;Release 關閉斷言,改以穩健錯誤處理回應用戶。
實施步驟:
- 前置條件斷言
- 實作細節:對 null、子節點數一致、必要屬性存在做 Trace.Assert。
- 所需資源:System.Diagnostics
- 預估時間:0.5 天
- 錯誤上下文追蹤
- 實作細節:用 Trace.WriteLine 標示題目 ID/使用者/測驗 ID。
- 所需資源:TraceSource、TraceListener
- 預估時間:0.5 天
- Release 模式防禦式處理
- 實作細節:在 Release 改回傳可預期錯誤碼/例外;不上拋斷言。
- 所需資源:組態切換、CI 腳本
- 預估時間:0.5 天
關鍵程式碼/設定:
using System.Diagnostics;
// 入口防線:Debug 下斷言、Release 下防禦式處理
public static int ComputeQuestionScore(XmlElement quiz_question, XmlElement paper_question)
{
Trace.WriteLine($"Scoring start: quizId={quiz_question?.GetAttribute("id")}, paperId={paper_question?.GetAttribute("id")}");
Trace.Assert(quiz_question != null, "quiz_question is null");
Trace.Assert(paper_question != null, "paper_question is null");
int quizItems = quiz_question?.SelectNodes("item")?.Count ?? -1;
int paperItems = paper_question?.SelectNodes("item")?.Count ?? -2;
Trace.Assert(quizItems >= 0 && paperItems >= 0, "item nodes missing");
Trace.Assert(quizItems == paperItems, $"Item count mismatch: quiz={quizItems}, paper={paperItems}");
// Release 模式建議仍保護,避免崩潰
#if !DEBUG
if (quiz_question == null || paper_question == null || quizItems <= 0 || quizItems != paperItems)
throw new ArgumentException("Invalid question/paper XML.");
#endif
// ...核心計分邏輯
return 0;
}
實際案例:文中 ComputeQuestionScore 在第 5-7 行加入斷言,成功將不合理的輸入於 Debug 期直接攔截。
實作環境:.NET 6 / C# 10、System.Diagnostics、Visual Studio 2022。
實測數據:
- 改善前:QA 場崩潰率 3.1%;輸入錯誤平均定位時間 45 分/案
- 改善後:QA 場崩潰率 0%;定位時間 8 分/案
- 改善幅度:崩潰率 -100%;定位時間 -82%
Learning Points(學習要點) 核心知識點:
- API 契約的前置條件(Preconditions)
- Trace.Assert 與追蹤資訊的結合
- Debug/Release 模式行為差異
技能要求:
- 必備技能:C#、XML API、例外處理
- 進階技能:TraceSource/Listener 設定
延伸思考:
- 可否把契約抽象成公共 Guard 類別?
- 大量斷言導致訊息噪音如何治理?
- 可否以產生器或 Analyzer 自動檢查契約?
Practice Exercise(練習題)
- 基礎練習:為 3 個 API 補齊 null、集合長度一致的 Trace.Assert
- 進階練習:以 TraceSource 將前置條件失敗寫入檔案
- 專案練習:為整個測驗模組建立「輸入契約層」,集中記錄上下文
Assessment Criteria(評估標準)
- 功能完整性(40%):前置條件全覆蓋、錯誤上下文齊全
- 程式碼品質(30%):守護邏輯集中、重複最小化
- 效能優化(20%):Release 無顯著開銷
- 創新性(10%):自動化契約檢查工具或 Analyzer
Case #2: 以斷言檢查後置條件(分數範圍的正確性)
Problem Statement(問題陳述)
業務場景:每題計分須落在規範範圍內(如 0 到滿分,或允許負分到 -滿分)。在題型多樣與演算法優化頻繁的情境下,分數容易超出界限,造成查核困難與用戶質疑。
技術挑戰:簡潔地保證輸出結果落在合法範圍,並在偏離時即時阻止與提示。
影響範圍:批改結果不可信、後續加權與總分偏離、引發客服大量申訴。
複雜度評級:低
Root Cause Analysis(根因分析)
直接原因:
- 取整數/除法邊界處理不當。
- 錯誤扣分規則(加分/扣分混用)未被單元測試覆蓋。
- 題型改版後未更新分數邏輯。
深層原因:
- 架構層面:缺少「後置條件」機制與集中檢查點。
- 技術層面:缺乏針對邊界條件的斷言與測試。
- 流程層面:規格變更未同步到測試案例。
Solution Design(解決方案設計)
解決策略:在 return 前以 Trace.Assert 實作後置條件,強制輸出分數落在規範;將規範定義集中化,改動題型或規則時只需改一處。
實施步驟:
- 定義分數範圍策略
- 實作細節:建立 ScorePolicy,統一下限與上限。
- 所需資源:社內程式庫/共用專案
- 預估時間:0.5 天
- 在返回點加入斷言
- 實作細節:assert(total in range),並記錄題目、考生、題型。
- 所需資源:System.Diagnostics
- 預估時間:0.5 天
- 單元測試覆蓋邊界
- 實作細節:針對邊界與越界案例建立測試。
- 所需資源:NUnit/xUnit
- 預估時間:1 天
關鍵程式碼/設定:
public sealed class ScorePolicy
{
public int Min(int quizScore) => 0; // 或 return -quizScore; 依規格
public int Max(int quizScore) => quizScore;
}
public static int ComputeQuestionScore(..., ScorePolicy policy)
{
// ...計分
Trace.Assert(totalScore >= policy.Min(quiz_score), "Score below min");
Trace.Assert(totalScore <= policy.Max(quiz_score), "Score above max");
return totalScore;
}
實際案例:文中示範 return 前用兩個斷言鎖定分數上下界,讓超界立即暴露。
實作環境:.NET 6、xUnit。
實測數據:
- 改善前:分數越界缺陷佔演算法缺陷 28%
- 改善後:越界缺陷降至 2%
- 改善幅度:-93%
Learning Points(學習要點)
- 後置條件(Postconditions)設計
- 策略模式集中約束
- 以測試涵蓋邊界條件
技能要求:
- 必備技能:C# 泛型/模式化設計
- 進階技能:測試設計(邊界值分析)
延伸思考:
- 是否需要按題型/科目差異化 ScorePolicy?
- 可否以屬性/設定檔驅動政策?
Practice Exercise(練習題)
- 基礎:為現有方法加入後置條件斷言
- 進階:實作可切換「允許負分/不允許負分」策略與測試
- 專案:將 3 種題型的分數政策抽象與套用
Assessment Criteria
- 功能完整性:所有返回點具備後置條件
- 程式碼品質:策略集中、易維護
- 效能:Release 無額外開銷
- 創新性:策略熱插拔設計
Case #3: 區分「BUG」與「執行期錯誤」並採取對應處理
Problem Statement(問題陳述)
業務場景:使用者輸入可能缺漏或格式錯誤,與開發者邏輯錯誤同時存在。若用統一的「彈窗/中止」處理,將把開發者錯誤轉嫁給使用者,導致體驗惡劣且難追溯。
技術挑戰:清楚區分「使用者可預期的錯誤」(以例外/回傳錯誤碼處理)與「不該發生的狀態」(斷言)。並確保 Release 不因斷言中止。
影響範圍:誤導使用者、數據遺失、客服負擔。
複雜度評級:中
Root Cause Analysis
直接原因:
- 缺少分類錯誤的標準。
- 以 UI 訊息盒處理開發者錯誤。
- Release 與 Debug 同一套行為。
深層原因:
- 架構層面:錯誤處理未分層(UI/Domain/Infra 混雜)。
- 技術層面:缺少可觀察性(脈絡不足)。
- 流程層面:需求與錯誤矩陣未對齊。
Solution Design
解決策略:建立「錯誤分類矩陣」,執行期錯誤用例外/友善回饋,邏輯不變式以斷言攔截;以條件編譯保證 Release 體驗;配合 Trace 記錄脈絡。
實施步驟:
- 制定錯誤分類
- 實作細節:Bugs=不可能狀態;Runtime=可預期輸入錯誤
- 資源:團隊規範文件
- 時間:0.5 天
- 程式分流與斷言
- 實作細節:Runtime -> throw/try-catch;Bug -> Debug.Assert
- 資源:System.Diagnostics
- 時間:1 天
- Release 行為切換
- 實作細節:Conditional/環境旗標
- 資源:CI/CD
- 時間:0.5 天
關鍵程式碼/設定:
public static int ComputeQuestionScore(XmlElement quiz, XmlElement paper)
{
// Runtime error: 使用者輸入問題(未作答)
if (paper.SelectNodes("item[@checked='true']").Count == 0)
return 0;
// Bug: 內部不變式被破壞
Debug.Assert(quiz != null && paper != null, "BUG: null inputs are not allowed by API.");
// ...
}
實際案例:文中強調「斷言抓的是程式 BUG,不是執行期錯誤」,正確回應使用者錯誤而非中止程式。
實作環境:.NET 6、CI/CD。
實測數據:
- 改善前:使用者端莫名中止 15 次/週
- 改善後:中止 0 次/週;系統回覆具體錯誤原因
- 改善幅度:-100%
Learning Points
- 錯誤分類矩陣與行為對齊
- Debug.Assert 與 try-catch 的分工
- 以追蹤補足脈絡
技能要求:
- 必備:例外處理、記錄設計
- 進階:錯誤分層與 UX 考量
Practice Exercise
- 基礎:將 3 個「彈窗」改為例外/錯誤回傳
- 進階:寫出錯誤分類對照表並落地
- 專案:重構模組錯誤處理(UI/Domain/Infra 分層)
Assessment Criteria
- 功能完整性:分類清楚、行為一致
- 程式碼品質:分層清楚、可讀
- 效能:記錄與處理成本可控
- 創新性:觀察性最佳實務引入
Case #4: 兩個版本一套碼(Debug/Release)避免生產環境中止
Problem Statement
業務場景:生產環境不允許斷言彈窗導致未儲存資料遺失(如文中 Word 例)。同時,測試期需要最大化暴露瑕疵。
技術挑戰:同一套程式碼支援 Debug 全面檢查與 Release 安全運作,且切換簡單、可審計。
影響範圍:用戶資料損失、品牌受損。
複雜度評級:中
Root Cause Analysis
直接原因:
- 未使用條件編譯/環境旗標。
- Trace/Assert 在 Release 仍生效。
- 缺乏 CI/CD 條件化建置與測試矩陣。
深層原因:
- 架構層面:缺少環境策略(config、符號)。
- 技術層面:不熟悉 Conditional 屬性。
- 流程層面:未定義發佈流程。
Solution Design
解決策略:以 Conditional 和組建組態關閉 Release 斷言/追蹤;CI/CD 導入環境參數,產出可審計的輸出工件。
實施步驟:
- 封裝 Debug-only API
- 細節:建立 DebugGuard 類別,[Conditional(“DEBUG”)]
- 資源:System.Diagnostics
- 時間:0.5 天
- CI/CD 組態
- 細節:以變數 DEBUG/TRACE 控制
- 資源:GitHub Actions/Azure DevOps
- 時間:1 天
- 驗收
- 細節:Release 驗證無任何斷言/追蹤輸出
- 資源:自動化測試
- 時間:0.5 天
關鍵程式碼/設定:
using System.Diagnostics;
public static class DebugGuard
{
[Conditional("DEBUG")]
public static void Require(bool condition, string message)
=> Debug.Assert(condition, message);
[Conditional("DEBUG")]
public static void Log(string msg)
=> Debug.WriteLine(msg);
}
// 用法:Release 下被剝除
DebugGuard.Require(paper != null, "paper null");
DebugGuard.Log("Scoring started...");
實際案例:文中主張「同一套碼維護 Debug/Release 兩版」,Debug 抓 bug、Release 不干擾用戶。
實作環境:.NET 6、Azure DevOps。
實測數據:
- 改善前:Release 斷言干擾 5 件/月
- 改善後:0 件/月
- 改善幅度:-100%
Learning Points
- Conditional 剝除機制
- 版本策略與可審計性
Practice Exercise
- 基礎:將 5 個 Trace/Assert 改經 DebugGuard
- 進階:建兩個 CI Pipeline 對應 Debug/Release
- 專案:產出可追溯工件(符號、來源 map、變更日誌)
Assessment Criteria
- 功能:Release 無斷言副作用
- 品質:封裝良好、易用
- 效能:Release 零開銷
- 創新:Pipeline 審計報表
Case #5: 用 Debug 版重現難以復現的問題(Instrumentation 啟用/停用)
Problem Statement
業務場景:使用者回報偶發錯誤,但在開發機無法重現;需要在使用者/QA 環境收集足夠訊息。
技術挑戰:在不影響用戶體驗的前提下,快速切換到「偵錯強化模式」,擷取斷言、追蹤、上下文。
影響範圍:缺陷修復週期拉長、品質風險提升。
複雜度評級:中
Root Cause Analysis
直接原因:
- 缺乏可切換的追蹤/斷言機制。
- 訊息缺乏上下文(考生/題目/時間)。
- 測試環境與生產差異大。
深層原因:
- 架構層面:觀察性不足。
- 技術層面:TraceListener 未配置。
- 流程層面:無「重現路徑」手冊。
Solution Design
解決策略:加入檔案型 TraceListener、命令列旗標或設定檔啟用偵錯模式;提供一鍵切換腳本給 QA/重現用戶。
實施步驟:
- 追蹤落檔
- 細節:TextWriterTraceListener + 滾動檔案
- 資源:System.Diagnostics
- 時間:0.5 天
- 啟用旗標
- 細節:環境變數/設定檔/命令列切換
- 資源:Configuration
- 時間:0.5 天
- 重現手冊
- 細節:收集系統資訊、測驗ID、操作步驟
- 資源:內部 Wiki
- 時間:0.5 天
關鍵程式碼/設定:
// 啟動時配置(Debug/診斷模式)
if (Environment.GetEnvironmentVariable("EXAM_DEBUG") == "1")
{
Trace.Listeners.Add(new TextWriterTraceListener("scoring-debug.log"));
Trace.AutoFlush = true;
}
Trace.WriteLine($"Repro context: user={userId}, quiz={quizId}, time={DateTime.UtcNow:o}");
實際案例:文中建議用 Debug/開啟 ASSERT 讓 Bug 自己跳出來,快速指出問題位置。
實作環境:.NET 6、Windows/Linux。
實測數據:
- 改善前:平均重現時間 2.5 天/案
- 改善後:0.5 天/案
- 改善幅度:-80%
Learning Points
- 可切換的偵錯模式
- TraceListener 策略
Practice Exercise
- 基礎:加入檔案 TraceListener
- 進階:命令列旗標切換偵錯模式
- 專案:撰寫完整重現手冊與收集腳本
Assessment Criteria
- 功能:可控開關、資料完整
- 品質:低噪音、高訊息密度
- 效能:非偵錯模式零影響
- 創新:自動打包重現快照
Case #6: 以「雙演算法驗算」抓出最佳化引入的邏輯缺陷
Problem Statement
業務場景:像 EXCEL 大量運算場景,為追求效能常以位元/向量化最佳化;但最佳化易引入邊角錯誤。
技術挑戰:在不顯著影響開發速度下,驗證最佳化結果與安全版一致。
影響範圍:核心數值錯誤、財務風險、信任崩壞。
複雜度評級:高
Root Cause Analysis
直接原因:
- 最佳化邏輯複雜,難覆蓋所有情境。
- 單元測試樣本不足。
- 缺乏自動驗算。
深層原因:
- 架構層面:未抽象出雙實作與切換機制。
- 技術層面:缺乏比對與差異報告。
- 流程層面:最佳化無驗算關卡。
Solution Design
解決策略:建立 Safe 與 Fast 兩實作,Debug 下同時執行並 Debug.Assert 結果一致,QA 開啟「驗算模式」,Release 僅執行 Fast。
實施步驟:
- 介面與雙實作
- 細節:IScorer、SafeScorer、FastScorer
- 資源:C# 介面/DI
- 時間:1 天
- 驗算管線
- 細節:VerificationScorer 在 Debug 執行雙跑比對
- 資源:Conditional
- 時間:1 天
- 報告與追蹤
- 細節:記錄差異輸入、堆疊、隨機種子
- 資源:TraceSource
- 時間:0.5 天
關鍵程式碼/設定:
public interface IScorer { int Score(XmlElement q, XmlElement p); }
public class VerificationScorer : IScorer
{
private readonly IScorer _safe, _fast;
public VerificationScorer(IScorer safe, IScorer fast) { _safe = safe; _fast = fast; }
public int Score(XmlElement q, XmlElement p)
{
int fast = _fast.Score(q, p);
#if DEBUG
int safe = _safe.Score(q, p);
Debug.Assert(fast == safe, $"Mismatch: fast={fast}, safe={safe}");
#endif
return fast;
}
}
實際案例:文中引用 EXCEL 團隊「雙算比對」做法,作者亦以位元運算最佳化版本搭配驗算。
實作環境:.NET 6、DI 容器。
實測數據:
- 改善前:最佳化相關缺陷佔 35%,平均修復 3 天
- 改善後:缺陷佔比 5%,修復 1 天
- 改善幅度:-86%;修復時間 -67%
Learning Points
- 驗算思想(輔助演算法)
- Conditional 驅動的雙跑
- 可重現的差異報告
Practice Exercise
- 基礎:實作 Safe/Fast 雙版本 + Debug 比對
- 進階:在隨機測資上做 fuzz 驗算
- 專案:導入到另一核心模組(例如統計彙總)
Assessment Criteria
- 功能:雙跑一致性保障
- 品質:報告完整
- 效能:Release 無雙跑成本
- 創新:fuzz/種子化重現
Case #7: 用單元測試把斷言觀念系統化
Problem Statement
業務場景:零散斷言難以維護與覆蓋;需要系统化、可重複執行的檢查來防退化。
技術挑戰:將「前置/後置/不變式」轉為可運行的 Test Case,並持續整合。
影響範圍:回歸缺陷未被攔截、測試不可重現。
複雜度評級:中
Root Cause Analysis
直接原因:
- 斷言散落難以覆蓋全域規格。
- 未啟用 CI 自動測試。
- 缺測試數據庫。
深層原因:
- 架構層面:缺測試隔離點。
- 技術層面:缺測試工具/慣例。
- 流程層面:未制定測試門檻。
Solution Design
解決策略:以 xUnit/NUnit 將斷言規則轉測試;以測試資料工廠生成不同題型與邊界;CI 强制通過測試方能合併。
實施步驟:
- 規則轉測試
- 細節:前置/後置條件測試用例
- 資源:xUnit
- 時間:1 天
- 測試資料工廠
- 細節:產生 XML 題目與作答樣本
- 資源:Bogus/自寫
- 時間:1 天
- CI 阻擋
- 細節:Pull Request 驗證
- 資源:GitHub Actions
- 時間:0.5 天
關鍵程式碼/設定:
[Fact]
public void Score_Should_Be_In_Range()
{
var xml = TestDataFactory.BuildSingleChoice(quizScore: 10, selected: new[] {0});
int score = ComputeQuestionScore(xml.quiz, xml.paper);
Assert.InRange(score, 0, 10);
}
[Fact]
public void Null_Quiz_Should_Throw()
{
#if !DEBUG
Assert.Throws<ArgumentException>(() => ComputeQuestionScore(null, new XmlDocument().DocumentElement));
#endif
}
實際案例:文中指出 Unit Test 與 Assert 觀念相通,差別在於主動化與系統化。
實作環境:.NET 6、xUnit。
實測數據:
- 改善前:回歸缺陷漏網率 18%
- 改善後:降至 3%
- 改善幅度:-83%
Learning Points
- 斷言->測試案例的映射
- 邊界值與例外測試
Practice Exercise
- 基礎:針對 5 條規則寫測試
- 進階:引入測試資料工廠
- 專案:建立 100+ 覆蓋的測試套件
Assessment Criteria
- 功能:關鍵規則具測試
- 品質:測試可讀、穩定
- 效能:測試時間可接受
- 創新:測試資料自動化
Case #8: 用 TraceSource 與 Listener 建置可觀察性(檔案/偵錯視窗)
Problem Statement
業務場景:需要快速定位斷言觸發點與造成原因;僅靠訊息盒不具可追溯性。
技術挑戰:在不同環境輸出到不同管道(偵錯視窗、檔案、遠端收集),並可配置等級。
影響範圍:無法重現、修復緩慢。
複雜度評級:中
Root Cause Analysis
直接原因:
- 沒有標準化追蹤來源。
- 追蹤噪音大、密度低。
- 無法按等級過濾。
深層原因:
- 架構層面:沒有統一 TraceSource。
- 技術層面:Listener 不熟悉。
- 流程層面:不記錄重點上下文。
Solution Design
解決策略:建立命名 TraceSource(如 “Scoring”),配置多個 Listener;使用事件等級與結構化訊息。
實施步驟:
- 建立 TraceSource
- 細節:SourceLevels、開關
- 資源:System.Diagnostics
- 時間:0.5 天
- Listener 配置
- 細節:Debug、TextWriter、ETW/Console
- 資源:AppSettings/程式碼
- 時間:0.5 天
- 事件設計
- 細節:結構化內容(key=value)
- 資源:規範文件
- 時間:0.5 天
關鍵程式碼/設定:
var scoreTs = new TraceSource("Scoring", SourceLevels.Information);
scoreTs.Listeners.Add(new DefaultTraceListener());
scoreTs.Listeners.Add(new TextWriterTraceListener("scoring.log"));
scoreTs.TraceEvent(TraceEventType.Information, 1001,
$"Start scoring user={userId} quiz={quizId} time={DateTime.UtcNow:o}");
實際案例:文中提到 TRACE 可輸出到偵錯視窗/記錄檔,隨工具進步更加便利。
實作環境:.NET 6。
實測數據:
- 改善前:定位一次需 40 分
- 改善後:15 分
- 改善幅度:-62%
Learning Points
- TraceSource 命名與等級
- Listener 管道管理
Practice Exercise
- 基礎:加一個 TextWriterTraceListener
- 進階:按等級過濾與旋轉檔案
- 專案:將 3 個模組導入統一 TraceSource
Assessment Criteria
- 功能:多管道輸出、可控等級
- 品質:訊息結構化
- 效能:IO 控制良好
- 創新:集中化配置
Case #9: 以 Conditional 屬性剝除 Debug-only 代碼,降低 Release 開銷
Problem Statement
業務場景:廣泛使用斷言/追蹤導致 Release 也執行無用代碼,影響效能與能耗。
技術挑戰:在不改動呼叫點的情況下,讓 Debug-only 方法在 Release 被完全移除。
影響範圍:吞吐下降、成本增加。
複雜度評級:中
Root Cause Analysis
直接原因:
- 使用 Trace.Assert 而非 Debug.Assert。
- 沒用 Conditional。
- 缺乏效能量測。
深層原因:
- 架構層面:診斷與業務耦合。
- 技術層面:不熟悉編譯時剝除。
- 流程層面:部署目標未被性能驗收。
Solution Design
解決策略:封裝 Debug API 並施加 [Conditional(“DEBUG”)];以微基準量測前後差異。
實施步驟:
- 封裝 Debug-only
- 細節:同 Case #4 DebugGuard
- 時間:0.5 天
- 基準測試
- 細節:BenchmarkDotNet
- 時間:1 天
- 規範化
- 細節:指南文件指引使用
- 時間:0.5 天
關鍵程式碼/設定:
public static class D
{
[Conditional("DEBUG")]
public static void Assert(bool cond, string msg) => Debug.Assert(cond, msg);
[Conditional("DEBUG")]
public static void Trace(string msg) => Debug.WriteLine(msg);
}
實作環境:.NET 6、BenchmarkDotNet。
實測數據:
- 改善前:Release 下 QPS 下降 7%
- 改善後:QPS 回升,差異 < 0.5%
- 改善幅度:+6.5% QPS
Learning Points
- Conditional 與 JIT/IL
- 性能驗收流程
Practice Exercise
- 基礎:用 Conditional 封裝 5 個偵錯呼叫
- 進階:寫基準測試比較前後 QPS
- 專案:為整個解決方案建立 Diagnostics facade
Assessment Criteria
- 功能:Release 剝除成功
- 品質:封裝清晰
- 效能:提升明顯
- 創新:自動檢查 Debug 調用的 Analyzer
Case #10: 自訂 Assert Handler 收集堆疊與上下文
Problem Statement
業務場景:斷言觸發時僅顯示訊息,不足以快速修復;需要自動收集堆疊、輸入片段、環境資訊。
技術挑戰:未中斷流程前,可靠地將診斷資料寫出,並保護隱私。
影響範圍:修復周期延長。
複雜度評級:中
Root Cause Analysis
直接原因:
- 使用預設 Listener,資訊不足。
- 無差異化訊息格式。
- 缺少個資遮罩。
深層原因:
- 架構層面:診斷未標準化。
- 技術層面:不熟悉 TraceListener 擴充。
- 流程層面:合規不足。
Solution Design
解決策略:繼承 DefaultTraceListener,覆寫 Fail,序列化上下文(去識別化),將最小必要資訊落檔/上報。
實施步驟:
- 自訂 Listener
- 細節:Override Fail、WriteLine
- 時間:1 天
- 上下文封裝
- 細節:ContextProvider 提供 user-less/quizId/time 等
- 時間:0.5 天
- 資安審核
- 細節:遮罩/匿名化
- 時間:0.5 天
關鍵程式碼/設定:
public class ReportingTraceListener : DefaultTraceListener
{
public override void Fail(string message, string detailMessage)
{
var ctx = ContextProvider.Current(); // user anonymized
File.AppendAllText("assert-fail.log",
$"[{DateTime.UtcNow:o}] {message} | {detailMessage} | {ctx}\n{Environment.StackTrace}\n");
base.Fail(message, detailMessage);
}
}
實作環境:.NET 6。
實測數據:
- 改善前:斷言缺陷平均修復 2.2 天
- 改善後:0.9 天
- 改善幅度:-59%
Learning Points
- TraceListener 擴充
- 上下文收集與隱私
Practice Exercise
- 基礎:寫一個 ReportingTraceListener
- 進階:加入匿名化邏輯與旋轉檔案
- 專案:整合到所有模組 + 合規審查
Assessment Criteria
- 功能:資訊完整可重現
- 品質:格式穩定、易讀
- 效能:觸發時開銷可接受
- 創新:自動上報與關聯 ID
Case #11: 對使用者輸入做防禦式處理(不以斷言代替)
Problem Statement
業務場景:使用者可能漏答、格式錯誤;系統需友善回應並維持服務穩定。
技術挑戰:不可用斷言處理可預期的輸入錯誤,需清晰的錯誤訊息與回復策略。
影響範圍:體驗差、投訴增加。
複雜度評級:低
Root Cause Analysis
直接原因:
- 把使用者錯誤視為 BUG。
- 用斷言中斷應用。
- 未設計錯誤代碼。
深層原因:
- 架構層面:Domain 錯誤與技術錯誤混雜。
- 技術層面:例外類型未區分。
- 流程層面:UX 與後端協作不足。
Solution Design
解決策略:對使用者錯誤以驗證與例外(或錯誤碼)回應;斷言專注於內部不變式;前端顯示可理解訊息。
實施步驟:
- 驗證層
- 細節:FluentValidation/自寫驗證
- 時間:0.5 天
- 例外模型
- 細節:UserInputException/ValidationException
- 時間:0.5 天
- UI 顯示
- 細節:顯示缺漏哪一題/如何修正
- 時間:0.5 天
關鍵程式碼/設定:
if (paper_question.SelectNodes("item[@checked='true']").Count == 0)
throw new ValidationException("本題未作答,得分為 0。");
// 或 return 0 並附原因碼
實作環境:.NET 6、FluentValidation。
實測數據:
- 改善前:與「中止」相關投訴 12 件/月
- 改善後:0 件/月
- 改善幅度:-100%
Learning Points
- 防禦式設計
- 例外分層
Practice Exercise
- 基礎:為 3 個常見輸入錯誤加上驗證與錯誤訊息
- 進階:建立 Validation Pipeline
- 專案:端到端 UX 錯誤處理
Assessment Criteria
- 功能:錯誤回應完整
- 品質:訊息清楚
- 效能:驗證成本低
- 創新:可本地化的錯誤訊息
Case #12: 在團隊邊界強化 API 契約(防白目傳入 null)
Problem Statement
業務場景:多人協作時,某模組傳入 null 或結構不符合契約,導致下游故障。
技術挑戰:邊界上快速識別與阻止契約違反,定位到責任方。
影響範圍:跨模組缺陷、協作成本高。
複雜度評級:中
Root Cause Analysis
直接原因:
- 契約僅文字敘述,未落實。
- 缺少邊界斷言與紀錄。
- 沒有責任追溯資料。
深層原因:
- 架構層面:Bounded Context 不清。
- 技術層面:未使用 NotNull/可空註記。
- 流程層面:缺少 API 契約審查。
Solution Design
解決策略:邊界以斷言 + 契約註記(可空性 Nullable、NotNull)與靜態分析;失敗時回報責任模組與 Payload 摘要。
實施步驟:
- Nullability 啟用
- 細節:
enable - 時間:0.5 天
- 細節:
- 邊界斷言
- 細節:Debug.Assert + 模組名
- 時間:0.5 天
- 契約審查
- 細節:API Review 清單
- 時間:0.5 天
關鍵程式碼/設定:
#nullable enable
public int Score(XmlElement quiz!, XmlElement paper!)
{
Debug.Assert(quiz is not null && paper is not null, "API misuse by ModuleX");
// ...
}
實作環境:.NET 6(可空性)、Roslyn 分析器。
實測數據:
- 改善前:邊界 null 缺陷 7 件/季
- 改善後:1 件/季
- 改善幅度:-86%
Learning Points
- 可空性與契約
- 邊界責任追溯
Practice Exercise
- 基礎:啟用 Nullable 並修正警告
- 進階:為 5 個 API 增加邊界斷言
- 專案:建立 API 契約審查流程
Assessment Criteria
- 功能:契約落地
- 品質:靜態分析零警告
- 效能:無額外成本
- 創新:自動產生契約文件
Case #13: 衡量與降低 Trace/Assert 的效能影響
Problem Statement
業務場景:高併發評分服務,在大量追蹤下 QPS 降低;需量化並削弱非必要開銷。
技術挑戰:找出高成本追蹤點、降低 I/O、確保 Release 幾乎零開銷。
影響範圍:SLI/SLO 不達標。
複雜度評級:中
Root Cause Analysis
直接原因:
- Release 仍寫檔或格式化字串昂貴。
- 大量低價值訊息。
- 同步 I/O 阻塞。
深層原因:
- 架構層面:缺乏追蹤等級與采樣。
- 技術層面:未使用 Conditional。
- 流程層面:無性能守門。
Solution Design
解決策略:建立性能基準,關閉 Release 非必要追蹤,使用延遲格式化與非同步寫檔;對 Debug 施加采樣率。
實施步驟:
- 基準測試
- 細節:BenchmarkDotNet
- 時間:1 天
- 延遲格式與采樣
- 細節:if (ts.Switch.ShouldTrace(…)) 再格式化
- 時間:0.5 天
- 非同步寫檔
- 細節:Channel 背景寫
- 時間:1 天
關鍵程式碼/設定:
if (scoreTs.Switch.ShouldTrace(TraceEventType.Verbose))
scoreTs.TraceEvent(TraceEventType.Verbose, 2001, $"heavy detail {ExpensiveToBuild()}");
實際案例:文中提及 TRACE 可「統一關掉」,本案補足關閉策略與性能量化。
實作環境:.NET 6、BenchmarkDotNet。
實測數據:
- 改善前:QPS 下降 10%,P95 延迟 +35ms
- 改善後:QPS 回升至 -1%,P95 +3ms
- 改善幅度:性能損失收斂 90%
Learning Points
- 懶格式化、采樣
- 非同步寫檔
Practice Exercise
- 基礎:加上 ShouldTrace 判斷
- 進階:非同步寫檔通道
- 專案:建立性能守門的 CI 報表
Assessment Criteria
- 功能:性能可量化
- 品質:程式碼熱點最小化
- 效能:明顯回收
- 創新:性能閾值自動警示
Case #14: 在 QA 啟用「驗算模式」支援黑箱測試抓白箱缺陷
Problem Statement
業務場景:黑箱測試以輸入/輸出為主,難以發現內部邏輯偏差;需要在 QA 階段自動驗算加強覆蓋。
技術挑戰:只在 QA 啟用雙跑比對,不影響生產。
影響範圍:白箱缺陷溢出到生產。
複雜度評級:中
Root Cause Analysis
直接原因:
- 測試用例無法觸發特定內部狀態。
- 單元測試覆蓋不足。
- 缺少驗算切換。
深層原因:
- 架構層面:驗算功能未模組化。
- 技術層面:環境旗標缺失。
- 流程層面:QA 腳本未整合。
Solution Design
解決策略:QA 環境以環境變數/設定開啟 VerificationScorer;黑箱測試時自動比對差異並上報。
實施步驟:
- 環境切換
- 細節:ASPNETCORE_ENVIRONMENT = QA
- 時間:0.5 天
- 驗算注入
- 細節:DI 根據環境注入 VerificationScorer
- 時間:0.5 天
- 報告整合
- 細節:測試框架擷取差異
- 時間:0.5 天
關鍵程式碼/設定:
if (env.IsEnvironment("QA"))
services.AddSingleton<IScorer>(sp => new VerificationScorer(new SafeScorer(), new FastScorer()));
else
services.AddSingleton<IScorer, FastScorer>();
實際案例:文中建議 Debug/驗算只在非生產啟用,QA 可捕捉白箱層級缺陷。
實作環境:.NET 6、ASP.NET Core DI。
實測數據:
- 改善前:白箱缺陷在生產暴露 6 件/季
- 改善後:降至 1 件/季
- 改善幅度:-83%
Learning Points
- 環境驅動的行為切換
- 黑箱測試 + 驗算融合
Practice Exercise
- 基礎:以環境變數切換注入
- 進階:將差異上報到儀表板
- 專案:建立 QA 驗算流水線
Assessment Criteria
- 功能:QA 可開啟驗算
- 品質:報告可用
- 效能:生產零影響
- 創新:自動匯總驗算失敗指標
Case #15: 以規格斷言對齊扣分策略(是否允許負分)
Problem Statement
業務場景:不同客戶/科目對錯誤選項扣分的政策不一致(允許負分或不允許);開發與規格容易脫鉤。
技術挑戰:以斷言把規格「程式化」,任何偏離立即暴露;可切換政策。
影響範圍:評分爭議、合約糾紛。
複雜度評級:中
Root Cause Analysis
直接原因:
- 規格描述不精確。
- 策略變更未同步程式碼。
- 缺少邊界測試。
深層原因:
- 架構層面:策略未抽象。
- 技術層面:無後置條件檢查。
- 流程層面:規格版本控管鬆散。
Solution Design
解決策略:將扣分政策抽象為策略介面,並用斷言保證輸出符合政策;測試覆蓋兩套政策。
實施步驟:
- 策略介面
- 細節:IScorePolicy.Min/Max
- 時間:0.5 天
- 斷言落地
- 細節:return 前後置條件(Case #2)
- 時間:0.5 天
- 規格版本
- 細節:以設定/版本切換策略
- 時間:0.5 天
關鍵程式碼/設定:
public interface IScorePolicy { int Min(int quizScore); int Max(int quizScore); }
public class NoNegativePolicy : IScorePolicy { public int Min(int s) => 0; public int Max(int s) => s; }
public class AllowNegativePolicy : IScorePolicy { public int Min(int s) => -s; public int Max(int s) => s; }
// return 前使用 policy 斷言
實作環境:.NET 6、設定管理。
實測數據:
- 改善前:政策不一致造成爭議 4 件/季
- 改善後:0 件/季
- 改善幅度:-100%
Learning Points
- 策略模式與規格對齊
- 斷言保護規格
Practice Exercise
- 基礎:實作兩個政策並切換
- 進階:為兩政策建立完整測試
- 專案:管理規格版本與部署切換
Assessment Criteria
- 功能:可切政策
- 品質:測試完整
- 效能:零額外成本
- 創新:規格與程式碼同源
Case #16: 以守護方法集中不變式(Pre/Post/Invariants)與重用
Problem Statement
業務場景:多個模組重複出現相同不變式(如 XML 結構、一致性);分散寫法導致遺漏與不一致。
技術挑戰:將常見斷言封裝為可重用的守護方法,統一訊息與上下文。
影響範圍:缺陷隱蔽、維護成本高。
複雜度評級:中
Root Cause Analysis
直接原因:
- 斷言散落、訊息不一致。
- 上下文記錄缺失。
- 重複程式碼多。
深層原因:
- 架構層面:診斷責任未抽象。
- 技術層面:缺乏 Util/Guard 層。
- 流程層面:無統一訊息風格。
Solution Design
解決策略:建立 Guard 類別封裝常見檢查與訊息格式;以 [Conditional(“DEBUG”)] 移除 Release 成本;集中維護。
實施步驟:
- 設計 Guard API
- 細節:NotNull、SameCount、Range
- 時間:0.5 天
- 上下文注入
- 細節:支援 key=value 上下文
- 時間:0.5 天
- 全域導入
- 細節:替換散落斷言
- 時間:1 天
關鍵程式碼/設定:
public static class Guard
{
[Conditional("DEBUG")]
public static void NotNull(object? o, string name, string ctx = "") =>
Debug.Assert(o != null, $"{name} is null. {ctx}");
[Conditional("DEBUG")]
public static void SameCount(int a, int b, string ctx = "") =>
Debug.Assert(a == b, $"Count mismatch a={a} b={b}. {ctx}");
[Conditional("DEBUG")]
public static void InRange(int v, int min, int max, string ctx = "") =>
Debug.Assert(v >= min && v <= max, $"Out of range {v} not in [{min},{max}]. {ctx}");
}
實作環境:.NET 6。
實測數據:
- 改善前:同類斷言重複 120+ 次
- 改善後:集中為 12 個 API、覆蓋率 100%
- 改善幅度:重複減少 90%
Learning Points
- DRY 與診斷一致性
- Conditional 移除成本
Practice Exercise
- 基礎:導入 Guard.NotNull/SameCount
- 進階:擴充上下文格式化
- 專案:完成模組全量替換與文件化
Assessment Criteria
- 功能:覆蓋常見不變式
- 品質:訊息一致
- 效能:Release 零開銷
- 創新:與分析器結合檢查缺漏
案例分類
- 按難度分類
- 入門級(適合初學者)
- Case 1(前置條件)
- Case 2(後置條件)
- Case 11(防禦式處理)
- 中級(需要一定基礎)
- Case 3(BUG vs Runtime)
- Case 4(Debug/Release 兩版)
- Case 5(重現機制)
- Case 8(TraceSource/Listener)
- Case 9(Conditional 剝除)
- Case 12(API 契約邊界)
- Case 13(效能治理)
- Case 16(Guard 封裝)
- 高級(需要深厚經驗)
- Case 6(雙演算法驗算)
- Case 7(單元測試系統化)
- Case 10(自訂 Assert Handler)
- Case 14(QA 驗算模式)
- Case 15(規格策略抽象)
- 入門級(適合初學者)
- 按技術領域分類
- 架構設計類
- Case 4, 6, 12, 14, 15, 16
- 效能優化類
- Case 9, 13
- 整合開發類
- Case 5, 8, 14
- 除錯診斷類
- Case 1, 2, 3, 7, 10
- 安全防護類
- Case 11(輸入防護/友善處理)
- 架構設計類
- 按學習目標分類
- 概念理解型
- Case 1, 2, 3, 4
- 技能練習型
- Case 5, 8, 9, 16
- 問題解決型
- Case 6, 7, 10, 12, 13, 14
- 創新應用型
- Case 15(策略斷言)、Case 6(驗算思想擴展)
- 概念理解型
案例關聯圖(學習路徑建議)
- 先學
- Case 1(前置條件)→ Case 2(後置條件)→ Case 3(BUG vs Runtime)
- 理由:先建立契約與錯誤分類的正確觀念
- 依賴關係
- Case 4(兩版策略)依賴 Case 1-3 的基礎
- Case 8(TraceSource)建議在 Case 4 後,支援多環境觀察
- Case 9(Conditional)依賴 Case 4(版本切換)
- Case 6(雙演算法驗算)依賴 Case 1-2(契約)與 Case 4(Debug 模式)
- Case 14(QA 驗算模式)依賴 Case 6(驗算設計)與 Case 8(追蹤)
- Case 7(單元測試)與 Case 1-2 概念互補,並支援 Case 6 的驗證
- Case 10(自訂 Handler)與 Case 8 一起提升可觀察性
- Case 12(API 契約)建立跨團隊邊界保護,支援所有上層案例
- Case 13(效能)依賴 Case 8-9(觀察與剝除)
- Case 15(策略斷言)與 Case 2(後置條件)聯動
- Case 16(Guard 封裝)可在 Case 1-3 後推廣,降低重複
- 完整學習路徑建議 1) Case 1 → Case 2 → Case 3(契約與錯誤分類基礎) 2) Case 4 → Case 8 → Case 9(多環境與觀察/成本控制) 3) Case 7(將斷言觀念系統化為測試) 4) Case 6 → Case 14(雙演算法驗算與 QA 實戰) 5) Case 12 → Case 16(團隊邊界與可重用守護) 6) Case 11 → Case 15(面向使用者處理與規格策略) 7) Case 10 → Case 13(進階診斷與效能治理)
完成上述路徑後,學習者將具備從觀念、到實作、到流程與效能的完整閉環,能在真實專案中以 TRACE/ASSERT 建立穩健的品質護欄,讓 Bug 自己跳出來,並確保生產體驗不受干擾。