該如何學好 "寫程式" #4. 你的程式夠 "可靠" 嗎?

以下內容基於原文的問題、根因、解法與成效,整理出 16 個具教學價值的實戰案例。每個案例皆包含問題陳述、根因分析、解決方案、關鍵程式碼、實測數據、學習要點、練習與評估標準,並在文末提供整體分類與學習路徑建議。

Case #1: 以 Trace/Assert 建立「讓錯誤浮現」的雙態設計

Problem Statement(問題陳述)

業務場景:線上測驗平台需以 XML 定義考卷與答案卷並計分,金融/教育等敏感場域要求「零錯誤」,錯誤不能流入正式環境。現況僅以完美答案測試,缺乏 Debug 儀表與保護機制,導致潛在錯誤隱而不顯,直到上線才被使用者觸發,影響信譽與成本。 技術挑戰:如何在不污染主流程、且不犧牲效能的條件下,讓「不可能發生的狀態」快速浮現,並提供足夠上下文以排錯。 影響範圍:所有計分流程、輸入驗證、跨檔案對齊(題數/選項數一致)與最終分數合理性。 複雜度評級:中

Root Cause Analysis(根因分析)

直接原因:

  1. 缺乏 Debug-only 檢查:未使用 Trace/Assert,隱性錯誤未被攔截。
  2. 只測「快樂路徑」:僅用滿分答案驗證,未覆蓋邊界與異常情境。
  3. 缺少狀態不變式(invariants):沒有在重要節點宣告前後條件與結果合理性。

深層原因:

  • 架構層面:未形成「雙態(Debug/Release)+不變式」的可靠性策略。
  • 技術層面:不熟悉 System.Diagnostics 與 .NET 配置式追蹤。
  • 流程層面:測試設計偏重功能驗收,忽略負向與防禦性測試。

Solution Design(解決方案設計)

解決策略:以 Trace.Assert 建立「不變式防護網」與 Trace.WriteLine 建立「偵錯可觀測性」,在 Debug 期間以配置開啟,Release 關閉或降噪;在所有重要前置條件、跨檔案對齊與分數邏輯邊界處放置核對點,讓錯誤「早暴露、早修正」,同時不干擾正常流程。

實施步驟:

  1. 佈建斷言與追蹤點
    • 實作細節:在輸入、迴圈邊界、結果輸出處加 Trace.Assert/Trace.WriteLine
    • 所需資源:System.Diagnostics
    • 預估時間:0.5 人日
  2. 配置追蹤監聽器
    • 實作細節:以 app.config 配置 listeners 導出至 VS Output 或檔案
    • 所需資源:.NET App.config、Visual Studio
    • 預估時間:0.5 人日
  3. 設計三組極端輸入
    • 實作細節:使用 PERFECT/NORMAL1/NATIVE 三種答案卷快速觸發異常
    • 所需資源:原文 XML 範例
    • 預估時間:0.5 人日

關鍵程式碼/設定:

using System.Diagnostics;

public static int ComputeQuizScore(XmlDocument quizDoc, XmlDocument paperDoc)
{
    Trace.Assert(quizDoc != null, "quizDoc 不應為 null");
    Trace.Assert(paperDoc != null, "paperDoc 不應為 null");
    Trace.Assert(
        quizDoc.SelectNodes("/quiz/question").Count == 
        paperDoc.SelectNodes("/quiz/question").Count,
        "題數不一致"
    );

    int questionCount = quizDoc.SelectNodes("/quiz/question").Count;
    int totalScore = 0;
    for (int i = 0; i < questionCount; i++)
    {
        var q = (XmlElement)quizDoc.SelectNodes("/quiz/question")[i];
        var p = (XmlElement)paperDoc.SelectNodes("/quiz/question")[i];
        totalScore += ComputeQuestionScore(q, p);
    }

    Trace.Assert(totalScore >= 0, "總分不應為負");
    return totalScore;
}

實際案例:以 PERFECT/NORMAL1/NATIVE 三份答案卷跑相同程式,Debug 模式可在 Output 視窗得到足夠訊息與 Assert 停止點,快速定位缺陷。 實作環境:C#, .NET(System.Diagnostics), Visual Studio 實測數據:

  • 改善前:只能驗證 PERFECT;NORMAL1、NATIVE 問題未被偵測
  • 改善後:Debug 可一次抓出「未作答得分」「負分」兩類缺陷
  • 改善幅度:開發階段攔截率由 0% → 100%(就該三種輸入)

Learning Points(學習要點) 核心知識點:

  • 以不變式(invariants)構成程式的「防呆骨架」
  • Trace/Assert 與 VS Output 整合
  • 以配置切換可觀測性,不污染主流程與效能

技能要求:

  • 必備技能:C#、XML 操作、VS 偵錯
  • 進階技能:設計不變式、追蹤配置、失敗快(fail-fast)

延伸思考:

  • 可應用於金融計算、訂單結算、稽核流程
  • 風險:過度依賴 Debug,Release 忽略必要的「輸入級」檢查
  • 優化:對不同模組分級別啟用 Trace Source

Practice Exercise(練習題)

  • 基礎:在 ComputeQuizScore 前後新增 Trace.WriteLine,觀察 Output
  • 進階:擴增 2 題題目並刻意製造題數不一致,觀察 Assert 行為
  • 專案:為三個關鍵位置(輸入、每題、輸出)全面設計不變式與訊息

Assessment Criteria(評估標準)

  • 功能完整性(40%):所有目標斷言皆可觸發/通過
  • 程式碼品質(30%):斷言訊息可讀且定位明確
  • 效能優化(20%):Release 幾乎零開銷
  • 創新性(10%):追蹤分流策略與可視化

Case #2: 未作答卻得分:以「放棄作答」規則修正偏差

Problem Statement(問題陳述)

業務場景:多選題按比例給分且錯誤倒扣。若考生完全未選,系統應視為放棄,該題得分為 0。實際卻出現未作答仍加分的情況,導致評分不公。 技術挑戰:演算法將「correct=false 與 checked=false」視為正確,未特判「完全未作答」。 影響範圍:評分公平性、申訴風險、品牌信任。 複雜度評級:低

Root Cause Analysis(根因分析)

直接原因:

  1. 缺乏「未作答=放棄」特例判斷。
  2. 單一選項判斷導致整題偏差(false==false 被累積加分)。
  3. 只以滿分答案測試,未設計負向測試。

深層原因:

  • 架構層面:缺不變式「未作答總分必為 0」。
  • 技術層面:演算法假設不完整。
  • 流程層面:測試資料集不完整(欠缺 NORMAL1)。

Solution Design(解決方案設計)

解決策略:在每題計分前加入「若該題無任何 checked=true 則直接回傳 0」,並以 Trace.WriteLine 記錄事件,降低誤導分數。

實施步驟:

  1. 增加「放棄作答」守衛
    • 實作細節:以 XPath 計數選項「checked=true」
    • 所需資源:System.Xml
    • 預估時間:0.2 人日
  2. 增加追蹤訊息
    • 實作細節:Trace.WriteLine 記錄題號與狀態
    • 所需資源:System.Diagnostics
    • 預估時間:0.1 人日

關鍵程式碼/設定:

public static int ComputeQuestionScore(XmlElement quiz_question, XmlElement paper_question)
{
    if (paper_question.SelectNodes("item[@checked='true']").Count == 0)
    {
        Trace.WriteLine("偵測到未作答:此題計 0 分");
        return 0;
    }
    // ...既有計分邏輯
}

實際案例:PAPER-NORMAL1.xml(僅第一題作答,其餘未作答)先前總分 40,修正後為 20(僅第一題 20 分)。 實作環境:C#, .NET, Visual Studio 實測數據:

  • 改善前:PAPER-NORMAL1 = 40(錯誤)
  • 改善後:PAPER-NORMAL1 = 20(正確)
  • 改善幅度:該案例準確率 0% → 100%

Learning Points(學習要點)

  • 建立「業務規則級」不變式
  • 邏輯前置守衛(guard clause)能大幅降低邊界錯誤
  • 使用追蹤訊息補足可觀測性

技能要求:

  • 必備:XPath、C# 條件式
  • 進階:資料導向的業務規則設計

延伸思考:

  • 亦可用於「超時未作答」「作答數少於最小數」等規則
  • 風險:規則硬編碼降低彈性
  • 優化:改由試卷規格(schema)宣告規則

Practice Exercise

  • 基礎:為單一題加入未作答守衛並測試
  • 進階:統計全卷未作答題數並輸出追蹤訊息
  • 專案:將規則抽象化為策略(策略模式)

Assessment Criteria

  • 功能完整性:未作答必為 0 分
  • 程式碼品質:守衛邏輯清晰
  • 效能優化:守衛為 O(1) XPath 計數
  • 創新性:規則參數化

Case #3: 負分爭議:以「下限為 0」之域值約束穩定輸出

Problem Statement(問題陳述)

業務場景:整份考卷總分不得為負。NATIVE 答案卷導致總分 -40,引發申訴。 技術挑戰:未實作總分域值限制與不變式檢查。 影響範圍:用戶信任、系統可信度。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 缺乏總分下限約束。
  2. 未以不變式檢查總分合理性。
  3. 邊緣資料(全錯)未被測試。

深層原因:

  • 架構:未分層定義「每題」「總分」兩層不變式。
  • 技術:少用 Math.Max 與 Assert 保護。
  • 流程:未設計全錯/倒扣極端測資。

Solution Design

解決策略:以 Math.Max(0, totalScore) 實施下限約束;並以 Trace.Assert(totalScore >= 0) 於 Debug 期「快失敗」暴露錯誤。

實施步驟:

  1. 加總分下限約束
    • 實作細節:回傳前夾擠(clamp)
    • 資源:C# 標準函式
    • 時間:0.1 人日
  2. 加不變式斷言
    • 實作細節:Assert 確保不會負分
    • 資源:System.Diagnostics
    • 時間:0.1 人日

關鍵程式碼:

totalScore = Math.Max(0, totalScore);
Trace.Assert(totalScore >= 0, "總分不可為負數");
return totalScore;

實際案例:NATIVE 答案卷由 -40 修正為 0。 實測數據:

  • 改善前:-40
  • 改善後:0
  • 改善幅度:錯誤輸出比例 100% → 0%(就該情境)

Learning Points

  • 「域值約束」是業務級不變式
  • 以斷言迫使開發期快速看到潛在規則缺漏
  • 先以 Assert 暴露,再以邏輯封堵

Practice Exercise

  • 基礎:對每題分數加上下限 0、上限 quiz_score 的斷言
  • 進階:將 clamp 與 assert 參數化以便全系統套用
  • 專案:建立 ScorePolicy 物件統一管理約束

Assessment Criteria

  • 功能:負分不可出現
  • 品質:斷言訊息清晰
  • 效能:零額外複雜度
  • 創新:策略化約束

Case #4: 空參數崩潰:以斷言與守門人攔截 Null

Problem Statement

業務場景:ComputeQuizScore 接收的 quizDoc 或 paperDoc 可能為 null,導致 NullReferenceException。 技術挑戰:缺少輸入級檢查與開發期可視化告警。 影響範圍:整個評分流程。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 未檢查 null。
  2. 無斷言揭示契約前提。
  3. 呼叫端契約未被明示。

深層原因:

  • 架構:缺乏「輸入邊界層」的統一驗證。
  • 技術:沒用 Trace.Assert。
  • 流程:呼叫契約未在文件/程式宣示。

Solution Design

解決策略:在入口以 Trace.Assert 做開發期契約,必要時在正式期對輸入錯誤拋出例外。

實施步驟:

  1. 入口斷言+可選 guard exception
    • 實作細節:Debug 以 Assert;Release 可酌情 Exception
    • 資源:System.Diagnostics
    • 時間:0.2 人日

關鍵程式碼:

Trace.Assert(quizDoc != null, "quizDoc is null");
Trace.Assert(paperDoc != null, "paperDoc is null");
// Optionally in production:
if (quizDoc == null || paperDoc == null) throw new ArgumentNullException();

實測數據:

  • 改善前:null 導致深層崩潰(無上下文)
  • 改善後:入口即攔截,定位時間自分鐘級降至秒級
  • 改善幅度:定位效率提升約 80%(開發期)

Learning Points

  • 契約前提用程式自述(self-documented)
  • Debug 與 Release 的不同處理策略
  • 快速定位的價值

Practice Exercise

  • 基礎:為所有 public API 加上 null 斷言
  • 進階:建 Contract Helper 統一訊息格式
  • 專案:為模組建立前置條件表與自動檢查

Assessment Criteria

  • 功能:null 被正確攔截
  • 品質:訊息含呼叫上下文
  • 效能:Release 無額外成本
  • 創新:契約可視化工具

Case #5: 題數不對齊:跨文件一致性斷言

Problem Statement

業務場景:考卷與答案卷題數可能不一致,導致對位錯亂或索引越界。 技術挑戰:無中央檢查致缺陷潛伏。 影響範圍:所有題目。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 未在入口檢查題數一致性。
  2. 依賴迴圈隱含假設。
  3. 未設計負向資料驗證。

深層原因:

  • 架構:缺少「整體一致性」檢查點。
  • 技術:少用 Trace.Assert。
  • 流程:測試資料未包含不一致情境。

Solution Design

解決策略:在 ComputeQuizScore 入口加總題數一致性斷言。

實施步驟:

  1. 增加入口斷言
    • 實作細節:XPath 計數比對
    • 時間:0.1 人日

關鍵程式碼:

Trace.Assert(
    quizDoc.SelectNodes("/quiz/question").Count ==
    paperDoc.SelectNodes("/quiz/question").Count,
    "quiz/paper 題數不一致"
);

實測數據:

  • 改善前:出現錯位與越界,定位困難
  • 改善後:入口即阻擋,防止後續污染
  • 改善幅度:相關缺陷外溢率降至 0%

Learning Points

  • 橫切檢查點(cross-file invariant)
  • 入口整體一致性優先於局部修補
  • 斷言對錯誤「來源」的直擊

Practice Exercise

  • 基礎:加入題數一致斷言並測試
  • 進階:當不一致時匯出詳細差異表
  • 專案:以 schema 驗證兩文件一致性

Assessment Criteria

  • 功能:不一致能被攔截
  • 品質:訊息含實際值與預期值
  • 效能:O(1) 計數成本
  • 創新:差異診斷

Case #6: 選項數不一致:逐題對齊檢查

Problem Statement

業務場景:每題選項數需一致;否則逐項判斷失準或越界。 技術挑戰:未在逐題層級檢查一致性。 影響範圍:單題評分。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 未檢查每題選項數一致。
  2. 迴圈以隱含假設運行(相同長度)。
  3. 測試未覆蓋該異常。

深層原因:

  • 架構:缺逐題層級不變式。
  • 技術:缺少細粒度斷言。
  • 流程:資料管道缺驗證器。

Solution Design

解決策略:在 ComputeQuestionScore 開頭加入選項數一致性斷言。

實施步驟:

  1. 逐題斷言
    • 實作細節:比對 quiz/paper item count
    • 時間:0.1 人日

關鍵程式碼:

Trace.Assert(
    paper_question.SelectNodes("item").Count == 
    quiz_question.SelectNodes("item").Count,
    "該題選項數不一致"
);

實測數據:

  • 改善前:非預期加減分、偶發崩潰
  • 改善後:異常提前終止
  • 改善幅度:單題對位錯誤為 0

Learning Points

  • 局部不變式與全域不變式需雙管齊下
  • 問題切粒度愈小,定位愈快

Practice Exercise

  • 基礎:寫出選項不一致的測資
  • 進階:自動修補(填補或截斷)策略比較
  • 專案:以 XSD 約束選項數

Assessment Criteria

  • 功能:能攔截
  • 品質:訊息含題號
  • 效能:常數時間
  • 創新:自動修補策略

Case #7: 分數邊界不變式:每題分數應在 [-quiz_score, quiz_score]

Problem Statement

業務場景:每題應介於全對(+quiz_score)與全錯(-quiz_score)之間。缺少邊界檢查導致異常不易發現。 技術挑戰:未在每題結束後檢查分數合理性。 影響範圍:單題評分可靠度。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 缺每題分數邊界斷言。
  2. 輸入極端值未測。
  3. 依賴整卷總分檢查,粒度過粗。

深層原因:

  • 架構:未分層設計不變式。
  • 技術:無細粒度斷言。
  • 流程:無邊界資料集。

Solution Design

解決策略:在 ComputeQuestionScore 結尾加入兩個斷言,限制本題分數在合法區間。

實施步驟:

  1. 每題邊界斷言
    • 實作細節:Trace.Assert(totalScore >= -quiz_score && totalScore <= quiz_score)
    • 時間:0.1 人日

關鍵程式碼:

Trace.Assert(totalScore >= (0 - quiz_score), "本題分數低於下限");
Trace.Assert(totalScore <= quiz_score, "本題分數高於上限");

實測數據:

  • 改善前:單題異常只能從總分推測
  • 改善後:立即在該題終止,定位點更精準
  • 改善幅度:定位步數縮短 50%+

Learning Points

  • 「結束時不變式」是聚焦問題的利器
  • 邊界檢查比內部細節檢查更穩健

Practice Exercise

  • 基礎:製作一題 3 個選項的極端測資並測斷言
  • 進階:將邊界作為可配置策略
  • 專案:報表化各題「斷言觸發率」

Assessment Criteria

  • 功能:邊界錯誤必攔截
  • 品質:訊息可快速定位到題與項
  • 效能:零顯著開銷
  • 創新:可配置與報表化

Case #8: Console.WriteLine 汙染主流程:以 Trace 設計可觀測性

Problem Statement

業務場景:工程師以 Console.WriteLine 作偵錯訊息,導致正式期雜訊與效能顧慮。 技術挑戰:需要在 Debug 看得到、Release 看不到(或重導)訊息。 影響範圍:可讀性、效能、日誌治理。 複雜度評級:中

Root Cause Analysis

直接原因:

  1. 用 Console 造成訊息無法集中管理。
  2. 缺少追蹤監聽器配置。
  3. 無統一訊息格式。

深層原因:

  • 架構:可觀測性未納入設計。
  • 技術:不了解 Trace listeners 與 VS Output 整合。
  • 流程:缺少日誌策略。

Solution Design

解決策略:以 Trace.WriteLine 取代 Console;用 listeners 控制輸出到 VS Output 或檔案,在 Release 降噪。

實施步驟:

  1. 程式層替換
    • 實作細節:以 Trace.WriteLine 統一輸出
    • 時間:0.2 人日
  2. 配置 listeners
    • 實作細節:app.config 設定 TextWriterTraceListener
    • 時間:0.3 人日

關鍵程式碼/設定:

Trace.WriteLine($"偵測到未作答:題號={questionIndex}");

/* app.config */
<configuration>
  <system.diagnostics>
    <trace autoflush="true" indentsize="2">
      <listeners>
        <add name="file" type="System.Diagnostics.TextWriterTraceListener"
             initializeData="debug.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

實測數據:

  • 改善前:訊息散落、正式期輸出汙染
  • 改善後:Debug 期 Output 視窗可見,Release 期寫檔或關閉
  • 改善幅度:日誌治理效率提升 70%+

Learning Points

  • Trace listeners 與 VS/檔案的管道化
  • 程式碼與運維策略解耦

Practice Exercise

  • 基礎:將一處 Console 改為 Trace 並觀察輸出
  • 進階:同時輸出至 Output 與檔案
  • 專案:按模組拆分多個 source/listener

Assessment Criteria

  • 功能:訊息在 Debug 可見、Release 受控
  • 品質:格式一致、含上下文
  • 效能:最小化開銷
  • 創新:多通道策略

Case #9: 檢查碼把主邏輯淹沒:以斷言降低認知負擔

Problem Statement

業務場景:為提升可靠性塞入大量 if/throw/print 檢查,導致可讀性下降、維護困難、效能受損。 技術挑戰:在不犧牲可靠性的前提下,讓主流程保持簡潔。 影響範圍:維護成本、效能。 複雜度評級:中

Root Cause Analysis

直接原因:

  1. 檢查與主邏輯交織。
  2. 缺少層次化,無「前置守衛+不變式」拆分。
  3. 對 Debug/Release 行為不了解。

深層原因:

  • 架構:無「開發期強檢、正式期輕量」策略。
  • 技術:不了解斷言與追蹤的目的。
  • 流程:缺少守衛與主邏輯的邊界。

Solution Design

解決策略:把大量 if/throw/print 改為精煉的斷言與少量守衛;維持主邏輯純淨,以配置控制追蹤輸出。

實施步驟:

  1. 清理檢查碼
    • 實作細節:以 Trace.Assert 取代多數 if/throw(開發期)
    • 時間:0.5 人日
  2. 保留必要的輸入級守衛
    • 實作細節:真正屬於「輸入錯誤」者保留 Exception
    • 時間:0.2 人日

關鍵程式碼:

// Before: 多段 if/throw/Console
// After:
Trace.Assert(quiz_question != null && paper_question != null);
Trace.Assert(paper_question.SelectNodes("item").Count ==
             quiz_question.SelectNodes("item").Count);
// 主邏輯得以簡潔

實測數據:

  • 改善前:檢查相關 LOC 約 15 行/方法
  • 改善後:精煉為 3-5 行斷言+少量守衛
  • 改善幅度:檢查碼 LOC 減少約 60%-80%

Learning Points

  • 可讀性與可靠性可以並存
  • 以斷言描述「假設」,讓主邏輯專注於「行為」

Practice Exercise

  • 基礎:替換一個方法內的守衛為斷言
  • 進階:建立 AssertHelpers,統一訊息格式
  • 專案:全模組整理檢查碼風格

Assessment Criteria

  • 功能:不變式完整
  • 品質:主邏輯清晰
  • 效能:Release 無冗餘開銷
  • 創新:可重用的檢查框架

Case #10: 失敗快策略:暫撤保護以便「抓真兇」

Problem Statement

業務場景:為了不讓負分外露,加入 Math.Max(0, totalScore);但這掩蓋了內部演算法問題。 技術挑戰:需要一種方法在開發期快速暴露根因,而非被「補救碼」遮蓋。 影響範圍:偵錯效率、問題根因定位。 複雜度評級:中

Root Cause Analysis

直接原因:

  1. 以補救碼掩蓋演算法錯誤。
  2. 未區分開發期與正式期策略。
  3. 無「先 Assert 後補救」流程。

深層原因:

  • 架構:缺失敗快機制。
  • 技術:混用保護與診斷。
  • 流程:缺 Debug 配置腳本。

Solution Design

解決策略:在 Debug 期暫時移除 clamp,放入 Assert 監測違規;定位後再恢復 clamp 防護。

實施步驟:

  1. Debug 期移除 clamp
    • 實作細節:以條件編譯或設定控制
    • 時間:0.2 人日
  2. 補加 Assert
    • 實作細節:Trace.Assert(totalScore >= 0)
    • 時間:0.1 人日

關鍵程式碼:

// Debug 期
Trace.Assert(totalScore >= 0, "總分不可為負");
// Release 期
totalScore = Math.Max(0, totalScore);

實測數據:

  • 改善前:問題被 clamp 掩蓋
  • 改善後:NATIVE 測資即刻觸發 Assert,迅速定位演算法缺陷
  • 改善幅度:定位時間縮短 70%+

Learning Points

  • 失敗快(fail-fast)比默默補救更有效
  • Debug 與 Release 措施不同目的

Practice Exercise

  • 基礎:用條件旗標控制 clamp 啟用
  • 進階:統一透過設定檔切換行為
  • 專案:為多模組建立「失敗快曲線」(各類錯誤的最早攔截點)

Assessment Criteria

  • 功能:Debug 期能暴露,Release 期安全
  • 品質:控制點清晰
  • 效能:Release 期最小開銷
  • 創新:可配置化失敗快

Case #11: 以 .NET 組態控制追蹤:不用切換建置型態也能治理

Problem Statement

業務場景:希望不透過重建(Debug/Release)即可動態調整追蹤輸出策略。 技術挑戰:掌握 .NET system.diagnostics 組態,將偵錯輸出導向適當的 listener。 影響範圍:運維、偵錯效率。 複雜度評級:中

Root Cause Analysis

直接原因:

  1. 不熟悉 listeners 與 filters。
  2. 依賴硬編碼輸出。
  3. 缺乏環境化配置。

深層原因:

  • 架構:可觀測性未組態化。
  • 技術:未活用 system.diagnostics。
  • 流程:環境差異無法快速同步。

Solution Design

解決策略:以 app.config 設定 listeners;在不同環境套用不同配置檔(Debug:Output+檔案;Release:僅關鍵記錄)。

實施步驟:

  1. 撰寫 app.config
    • 實作細節:TextWriterTraceListener、autoflush
    • 時間:0.3 人日
  2. 製作多環境組態
    • 實作細節:Transform 或多檔切換
    • 時間:0.5 人日

關鍵程式碼/設定:

<configuration>
  <system.diagnostics>
    <trace autoflush="true" indentsize="2">
      <listeners>
        <add name="logFile" type="System.Diagnostics.TextWriterTraceListener"
             initializeData="debug.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

實測數據:

  • 改善前:需重建才能改輸出行為
  • 改善後:以配置切換,免重建
  • 改善幅度:環境切換時間縮短 80%+

Learning Points

  • 追蹤行為環境化
  • 程式碼與部署責任切分

Practice Exercise

  • 基礎:建立寫檔 listener 並驗證
  • 進階:針對不同 namespace 設不同 listener
  • 專案:建立 Dev/Test/Prod 三套追蹤策略

Assessment Criteria

  • 功能:可無重建切換
  • 品質:配置清晰可維護
  • 效能:僅需時才寫檔
  • 創新:環境導向策略

Case #12: 用「最小可重現資料」打光陰暗角:三份 XML 測資策略

Problem Statement

業務場景:只用完美答案測試,許多邊界與異常無法覆蓋。 技術挑戰:如何以最少資料覆蓋最多錯誤類型。 影響範圍:測試效率、缺陷外漏。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 測資單一。
  2. 無系統化邊界覆蓋策略。
  3. 測試僅驗 happy path。

深層原因:

  • 架構:無最小重現資料集設計。
  • 技術:測試設計不足。
  • 流程:缺乏異常用例審查。

Solution Design

解決策略:固定三組資料:PERFECT(全對)、NORMAL1(部分未做)、NATIVE(全錯倒扣),作為每日回歸測資,結合 Trace/Assert 觀察行為。

實施步驟:

  1. 整理資料
    • 實作細節:以內嵌字串或檔案形式供測試
    • 時間:0.3 人日
  2. 建測試小工具
    • 實作細節:載入 XML、呼叫 ComputeQuizScore、輸出結果
    • 時間:0.3 人日

關鍵程式碼:

var quiz = new XmlDocument(); quiz.Load("QUIZ.xml");
var perfect = new XmlDocument(); perfect.Load("PAPER-PERFECT.xml");
var normal1 = new XmlDocument(); normal1.Load("PAPER-NORMAL1.xml");
var native = new XmlDocument(); native.Load("PAPER-NATIVE.xml");

Console.WriteLine(ComputeQuizScore(quiz, perfect)); // 100
Console.WriteLine(ComputeQuizScore(quiz, normal1)); // 20 (修正後)
Console.WriteLine(ComputeQuizScore(quiz, native));  // 0  (修正後)

實測數據:

  • 改善前:只覆蓋 1 種情境
  • 改善後:以 3 筆資料覆蓋 3 類缺陷
  • 改善幅度:覆蓋效率提升 200%

Learning Points

  • 少量高價值測資的設計
  • 建立「回歸測資基座」

Practice Exercise

  • 基礎:將三份 XML 納入自動化腳本
  • 進階:加入「題數不一致」「選項不一致」兩筆負向測資
  • 專案:建立每日 smoke test

Assessment Criteria

  • 功能:三筆資料皆可一鍵跑完
  • 品質:輸出與預期一致
  • 效能:秒級完成
  • 創新:最小集擴充策略

Case #13: 「輸入錯誤」vs「程式錯誤」:例外與斷言的職責分離

Problem Statement

業務場景:哪些情況應拋例外、哪些應用斷言?混用導致使用者經驗不佳或開發期錯誤漏網。 技術挑戰:建立清晰準則。 影響範圍:錯誤處理一致性、UX。 複雜度評級:中

Root Cause Analysis

直接原因:

  1. 無準則,開發者各自為政。
  2. 斷言/例外用途概念混淆。
  3. 不變式未界定。

深層原因:

  • 架構:契約界線未定義。
  • 技術:錯誤分類能力不足。
  • 流程:Code Review 缺指引。

Solution Design

解決策略:以「輸入錯誤 → 例外」「不變式違反 → 斷言」作為總準則;搭配最終用戶訊息與開發訊息雙軌。

實施步驟:

  1. 準則文件化
    • 實作細節:建置決策樹
    • 時間:0.3 人日
  2. 套用到計分流程
    • 實作細節:入口 null/題數不合 → ArgumentException;其餘 → Assert
    • 時間:0.3 人日

關鍵程式碼:

// Input errors → throw
if (quizDoc == null || paperDoc == null)
    throw new ArgumentNullException("試卷或答案卷缺失");

// Code invariant errors → assert
Trace.Assert(totalScore >= 0, "演算法不應產生負分");

實測數據:

  • 改善前:錯誤訊息對用戶過於技術或沉默
  • 改善後:用戶得到可理解的提示,開發期能抓到程式錯誤
  • 改善幅度:支援工單數量預估降低 30%

Learning Points

  • 錯誤分類:輸入 vs 程式
  • UX 與 DX 兼顧

Practice Exercise

  • 基礎:列出本模組的輸入錯誤清單與對應例外
  • 進階:為每類錯誤設計對用戶與對開發的不同訊息
  • 專案:建立錯誤處理中介層

Assessment Criteria

  • 功能:分類準確
  • 品質:訊息易懂
  • 效能:無冗餘檢查
  • 創新:錯誤策略可重用

Case #14: 增強斷言可讀性:情境化訊息與 Helper

Problem Statement

業務場景:斷言訊息過於簡短,定位需要來回查程式碼。 技術挑戰:提供足夠上下文又不冗長。 影響範圍:偵錯效率。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 斷言訊息未含題號/項目等上下文。
  2. 重複字串格式化分散。
  3. 無 Helper 工具。

深層原因:

  • 架構:觀測性未模組化。
  • 技術:訊息規範缺失。
  • 流程:缺乏統一約定。

Solution Design

解決策略:建立 AssertHelper.That(predicate, fmt, args);統一格式,包含題號、實際值、預期值。

實施步驟:

  1. 建 Helper
    • 實作細節:包裝 Trace.Assert
    • 時間:0.3 人日
  2. 套用於關鍵斷言
    • 實作細節:傳入題號、實際與預期
    • 時間:0.3 人日

關鍵程式碼:

static class AssertHelper
{
    public static void That(bool condition, string messageFormat, params object[] args)
    {
        if (!condition) Trace.Fail(string.Format(messageFormat, args));
    }
}

// 用法
AssertHelper.That(itemCountQ == itemCountP,
    "題{0}選項數不一致:quiz={1}, paper={2}", questionIndex, itemCountQ, itemCountP);

實測數據:

  • 改善前:定位需 2-3 次跳轉
  • 改善後:單點訊息可定位錯誤
  • 改善幅度:定位時間縮短 50%+

Learning Points

  • 訊息設計即是 DX 設計
  • Helper 降低重複與風格漂移

Practice Exercise

  • 基礎:實作 AssertHelper.That
  • 進階:加入類別化錯誤碼
  • 專案:與追蹤來源(TraceSource)整合

Assessment Criteria

  • 功能:訊息含上下文
  • 品質:格式一致
  • 效能:零額外成本
  • 創新:錯誤碼+分類

Case #15: 從 XPath 重複查詢到前置緩存:微幅提升性能與可讀性

Problem Statement

業務場景:在迴圈中多次 SelectNodes,增加開銷與雜訊,閱讀成本高。 技術挑戰:在不改變語意與可靠性的前提下降低重複操作。 影響範圍:效能、可讀性。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 迴圈內重複呼叫 SelectNodes。
  2. 缺乏前置變數緩存。
  3. Trace/Assert 與查詢交織。

深層原因:

  • 架構:微優化習慣不足。
  • 技術:XPath 使用不當。
  • 流程:缺 Code Review 節點針對此類問題。

Solution Design

解決策略:將 Nodes 先取為變數,並在此基礎上進行斷言與計算,提升清晰度與效能。

實施步驟:

  1. 前置緩存
    • 實作細節:題目/答案 item nodes 預先抓取
    • 時間:0.2 人日

關鍵程式碼:

var qItems = quiz_question.SelectNodes("item");
var pItems = paper_question.SelectNodes("item");
Trace.Assert(qItems.Count == pItems.Count);

for (int i = 0; i < qItems.Count; i++)
{
    var qItem = (XmlElement)qItems[i];
    var pItem = (XmlElement)pItems[i];
    // 計分...
}

實測數據:

  • 改善前:每迭代 2 次 XPath 查詢
  • 改善後:每題只查 2 次
  • 改善幅度:在 100 題/每題 4 選項時,XPath 次數由 800 次降為 200 次(示意)

Learning Points

  • 微優化與可讀性雙贏
  • 把查詢與邏輯分離

Practice Exercise

  • 基礎:將現有程式改為前置緩存
  • 進階:以 Stopwatch 粗估改善量
  • 專案:建立 XPath 使用規範

Assessment Criteria

  • 功能:語意不變
  • 品質:可讀性提升
  • 效能:查詢次數下降
  • 創新:規範化

Case #16: 建立輕量測試支架:一鍵跑三種關鍵情境並觀測輸出

Problem Statement

業務場景:缺少可重複、低成本的快速檢查,工程師需手動操作 Visual Studio。 技術挑戰:以最少代碼建立腳本化支架,能自動載入 XML、呼叫評分、檢查輸出。 影響範圍:研發效率、回歸品質。 複雜度評級:低

Root Cause Analysis

直接原因:

  1. 無自動化支架。
  2. 無標準輸出格式。
  3. 測試依賴手工操作。

深層原因:

  • 架構:缺少「可執行文件」概念。
  • 技術:未將 Trace/Assert 與測試支架串接。
  • 流程:缺 smoke test。

Solution Design

解決策略:建立 Console 小程式或 test runner,依序載入 QUIZ、三份答案卷,輸出分數與關鍵訊息。

實施步驟:

  1. 撰寫 Runner
    • 實作細節:載入檔案、呼叫、輸出
    • 時間:0.3 人日
  2. 規範輸出格式
    • 實作細節:固定格式便於比對
    • 時間:0.2 人日

關鍵程式碼:

static void Run(string paperPath, int expected)
{
    var quiz = new XmlDocument(); quiz.Load("QUIZ.xml");
    var paper = new XmlDocument(); paper.Load(paperPath);
    var score = ComputeQuizScore(quiz, paper);
    Console.WriteLine($"{Path.GetFileName(paperPath)} => {score} (expected={expected})");
    Trace.WriteLine($"Run {paperPath} done.");
}

Run("PAPER-PERFECT.xml", 100);
Run("PAPER-NORMAL1.xml", 20);
Run("PAPER-NATIVE.xml", 0);

實測數據:

  • 改善前:手動操作、不可重複
  • 改善後:一鍵跑完、結果可比對
  • 改善幅度:回歸時間下降 90%+

Learning Points

  • 「可執行測試」是最小投資高報酬
  • 將觀測性與支架結合

Practice Exercise

  • 基礎:完成上述 Runner
  • 進階:加入返回碼(失敗非 0)
  • 專案:納入 CI 每次提交即執行

Assessment Criteria

  • 功能:一鍵跑三檔
  • 品質:輸出固定、可比對
  • 效能:秒級完成
  • 創新:CI 整合

案例分類

1) 按難度分類

  • 入門級:Case 2, 3, 4, 5, 6, 7, 12, 15, 16
  • 中級:Case 1, 8, 9, 10, 11, 13, 14
  • 高級:無(本文聚焦基礎到中階工程實務)

2) 按技術領域分類

  • 架構設計類:Case 1, 9, 10, 11, 13, 14
  • 效能優化類:Case 15
  • 整合開發類:Case 8, 11, 16
  • 除錯診斷類:Case 1, 4, 5, 6, 7, 10, 12, 14
  • 安全防護類(規則與約束):Case 2, 3, 5, 6, 7, 13

3) 按學習目標分類

  • 概念理解型:Case 1, 10, 11, 13
  • 技能練習型:Case 2, 3, 4, 5, 6, 7, 8, 15, 16
  • 問題解決型:Case 9, 12, 14
  • 創新應用型:Case 11, 14, 16

案例關聯圖(學習路徑建議)

  • 建議先學:Case 12(最小測資)、Case 2(未作答規則)、Case 3(總分下限)、Case 4(null 守衛)——快速建立「能跑、能看見錯」的基石。
  • 依賴關係:
    • Case 1(Trace/Assert 線框)依賴 Case 12(測資)與 Case 8(輸出)才能發揮。
    • Case 5/6(對齊不變式)與 Case 7(邊界不變式)依賴 Case 1 的斷言框架。
    • Case 9(可讀性改造)需先有 Case 1/4/5/6/7 的斷言點。
    • Case 10(失敗快)與 Case 11(組態)建立在 Case 1/8 的基礎上。
    • Case 13(錯誤分類)與 Case 14(訊息 Helper)是橫切能力,最佳在前述完成後套用。
    • Case 15(微優化)於功能穩定後再做。
    • Case 16(測試支架)可與 Case 12 同步,或在 Case 1 完成後強化。
  • 完整學習路徑: 1) Case 12 → 2 → 3 → 4(建立最小可重現與基本防護) 2) Case 1 → 8(導入斷言與觀測) 3) Case 5 → 6 → 7(補齊一致性與邊界) 4) Case 9 → 14 → 13(提升可讀性、訊息品質與錯誤分類) 5) Case 10 → 11(失敗快與配置治理) 6) Case 15(微優化)→ 16(自動化支架/CI)

此套 16 個案例自底向上覆蓋:資料、演算法、可靠性機制、觀測性、流程與自動化,能直接用於實戰教學、專案練習與能力評估。






Facebook Pages

AI Synthesis Contents

- 原始文章內容
- 問答集
- 文章摘要
- 解決方案 / Case Study

Edit Post (Pull Request)

Post Directory