該如何學好 “寫程式” #5. 善用 TRACE / ASSERT
問題與答案 (FAQ)
Q&A 類別 A: 概念理解類
A-Q1: 什麼是 TRACE?
- A簡: 除錯訊息的專用通道,與一般輸出分離,可集中記錄並可一鍵關閉以利除錯。
- A詳: TRACE是為除錯而設計的訊息輸出機制,源於C系統的巨集,目的在用不同於一般輸出的方法,把診斷資訊送到適合的地方(例如IDE的輸出視窗、檔案、事件檢視器)。好處是能統一開關、集中觀察、不干擾正常輸出,讓開發與測試更有效率。在實務上應分類管理輸出層級與來源,避免雜訊,並於發佈版視需求關閉或降噪。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q10, B-Q3, C-Q4
A-Q2: 什麼是 ASSERT?
- A簡: 條件為假時中斷或警示的保護檢查,用於揭露程式邏輯錯誤與假設失效。
- A詳: ASSERT(斷言)接收一個布林條件,為真則無事發生,為假則在除錯環境下中斷或發出顯著警告。其核心用途是把「應該永遠成立的假設」明確化,包含前置條件、後置條件與不變條件,讓潛在BUG主動浮出。它不應取代執行期錯誤處理(如使用者輸入錯誤),而是鎖定開發者的程式錯誤。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q5, A-Q6, B-Q2, C-Q1
A-Q3: 為什麼需要同時維護 DEBUG/RELEASE 兩個版本?
- A簡: 讓DEBUG版打開TRACE/ASSERT抓錯,RELEASE版關閉雜訊,避免干擾使用者。
- A詳: 兩版策略可兼顧偵錯與使用者體驗。DEBUG版保留大量TRACE、啟用ASSERT、甚至加入驗算機制,使錯誤自動現形;RELEASE版則關閉或降低TRACE、移除或抑制ASSERT,避免程式意外中止與訊息外洩,同時保留必要的錯誤處理與穩定性。藉由條件編譯與建置設定,同一份程式碼可產生兩個適用於不同情境的發行物。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q1, B-Q4, C-Q3, D-Q1
A-Q4: 使用 TRACE/ASSERT 的核心價值是什麼?
- A簡: 讓錯誤自動跑出來、縮短定位時間、提升可診斷性與程式可靠度。
- A詳: 核心價值在於「主動揭露」與「快速診斷」。TRACE提供背景脈絡(參數、流程、狀態),ASSERT把不應發生之事定義清楚,失敗即刻警示,中斷在最接近錯誤源的地方。兩者合用,等於在程式布下眼線,縮短從症狀到根因的距離,強化穩定性與信心,同時不干擾RELEASE下的正常使用體驗。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q17, B-Q8, C-Q6
A-Q5: ASSERT 與執行期錯誤處理有何差異?
- A簡: ASSERT抓程式BUG;錯誤處理面向使用者輸入與外部狀況,須友善回應。
- A詳: ASSERT針對「永不該發生」的內部假設失效,失敗時應讓開發者介入(中斷、記錄)。執行期錯誤處理面向可預期的外部錯誤(如輸入無效、資源缺失),需回傳可理解訊息與補救流程。兩者不可混用:用ASSERT處理使用者錯誤會傷害體驗;忽略ASSERT則讓程式 BUG 潛伏更久。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q10, D-Q7, A-Q2
A-Q6: 什麼是前置、後置與不變條件(設計合約)?
- A簡: 入口必備條件、出口保證與過程不變的狀態約束,常以ASSERT落地。
- A詳: 前置條件(Precondition)界定呼叫前必須成立的條件(如參數非空、範圍正確);後置條件(Postcondition)保證函式完成後的結果特性(如回傳值範圍);不變條件(Invariant)描述執行過程需維持的狀態一致性。以ASSERT將這些契約明文化,讓違反時立即顯示出根因。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q5, C-Q1, C-Q2
A-Q7: 在哪裡最應該加上 ASSERT?
- A簡: 函式入口參數、重要中繼狀態、迴圈不變量、回傳值範圍等關鍵點。
- A詳: 典型斷言點包含:入口檢查(非空、尺寸一致)、流程關鍵轉換後的狀態一致性、迴圈不變條件與邊界、演算法臨界值、資源取得釋放平衡,以及回傳值範圍。這些位置最能在錯誤初現時攔截,避免錯誤蔓延成難追的異常行為。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q6, B-Q5, C-Q2
A-Q8: TRACE 與一般應用層日誌有何差異?
- A簡: TRACE偏診斷與開發可觀測性;應用日誌偏營運稽核與商務事件。
- A詳: TRACE多用於開發與測試階段的除錯資訊,粒度細、包含內部細節,通常可由編譯或設定快速開關;應用層日誌對營運者有價值,記錄使用者行為、交易、錯誤摘要,保留期長、合規需求高。兩者應分層管理,避免敏感內部資訊洩漏至正式環境。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q3, B-Q19, C-Q4
A-Q9: Debug.Assert 與 Trace.Assert 的差異是什麼(.NET)?
- A簡: Debug受DEBUG符號控制,常於除錯;Trace受TRACE符號控制,輸出可設定。
- A詳: 在.NET中,Debug.方法標記為Conditional(“DEBUG”),預設只在Debug建置生效;Trace.受Conditional(“TRACE”)控制,依建置設定而定(常於Debug與Release皆可開啟)。實務上用Debug.Assert避免進正式版,用Trace配合TraceListener在不同環境彈性輸出;最終以建置設定決定保留與否。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q1, B-Q3, C-Q3
A-Q10: 為什麼 TRACE 輸出要與一般輸出分離?
- A簡: 降低干擾、便於集中蒐集與關閉,避免影響使用者與效能。
- A詳: 將診斷訊息從標準輸出或UI分離,可維持介面乾淨、避免誤導使用者,並能依環境切換輸出管道(IDE視窗、檔案、事件紀錄)。此外,集中開關與等級管理讓你能在需要時擴充細節,在正式運行時降噪與控管開銷。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q1, B-Q3, C-Q4
A-Q11: 使用 ASSERT 會拖慢開發嗎?
- A簡: 短期多些檢查,長期大幅縮短找錯時間,反而加速整體開發。
- A詳: 斷言像「煞車」,不是讓車變慢,而是讓你敢開更快。雖然撰寫與維護斷言需投入心力,但它能在錯誤最靠近源頭時攔截,省下大量追蹤時間,降低迭代風險。配合單元測試與TRACE,整體開發速度與品質都會提升。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q15, B-Q8, C-Q6
A-Q12: 為何 ASSERT 不該用來處理使用者輸入錯誤?
- A簡: 使用者錯誤屬可預期狀況,應回應友善訊息與補救,不該中止程式。
- A詳: 斷言針對程式設計的假設失效(BUG),使用者輸入錯誤則屬業務流程的一部分,應以驗證規則、例外處理與錯誤訊息引導修正。混用會導致正式環境跳出警示或中止,傷害體驗並可能造成資料遺失。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q5, D-Q1, D-Q7
A-Q13: ASSERT 可能造成哪些風險?
- A簡: 若未關閉即發佈,可能中止應用、暴露內部資訊、影響使用者。
- A詳: 風險包含:正式環境觸發造成應用中止或彈窗、輸出內部狀態與堆疊而外洩敏感資訊、降低可用性。應透過建置設定、環境開關與合適的TraceListener管理,確保正式版行為可控且不打擾使用者。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q3, D-Q1, B-Q4
A-Q14: 什麼是「驗算」策略(安全版與最佳化版比對)?
- A簡: 以穩健慢速實作與高速最佳化各算一次,差異即抓出潛在BUG。
- A詳: 先寫一個邏輯單純、易正確的安全版,再寫一個效能最佳化版,於DEBUG階段讓兩者在相同輸入下運算並比對結果。若不一致即觸發ASSERT。此法成本較高,但能在關鍵演算法上顯著提升信心,特別適合「絕不中錯」場域。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q7, C-Q5, D-Q8
A-Q15: ASSERT 與單元測試的關係?
- A簡: 均為讓錯誤早現形;ASSERT偏運行期檢查,單元測試系統化驗證。
- A詳: ASSERT把契約內嵌於程式,在運行時監看「不應發生之事」;單元測試則將需求情境化為可重複的測試案例,主動驗證輸入輸出。兩者互補:斷言提高程式內在可觀測性;測試提供外在覆蓋度與回歸保障。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q8, C-Q7, A-Q11
A-Q16: TRACE/ASSERT 如何協助處理難以重現的BUG?
- A簡: TRACE保留現場脈絡,ASSERT鎖定假設失效點,縮短定位時間。
- A詳: 難重現BUG的關鍵在缺乏脈絡。啟用適當TRACE層級可捕捉當時關鍵參數、路徑與狀態,而ASSERT一旦觸發,能把程式停在最接近問題源的點。再配合使用者重現步驟,快速聚焦與修復。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: D-Q9, B-Q9, C-Q8
A-Q17: 何謂「讓錯誤自動跑出來」的設計思維?
- A簡: 以斷言與追蹤布建觀測點,違規即報,避免錯誤靜默擴散。
- A詳: 不等待錯誤浮上表面,而是在關鍵假設處加斷言,在流程關鍵點加追蹤,讓任何不一致立即被看見。此思維將遠因就地捕捉,減少日後因症狀多樣化而難以追查的修復成本。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q4, B-Q5, C-Q2
A-Q18: 在算分範例中,ASSERT 的角色是什麼?
- A簡: 驗證參數非空與子節點數量一致,回傳分數落於合法範圍。
- A詳: 範例於函式入口確認兩個XmlElement皆非空且子節點數一致;過程中維持狀態一致性;結尾以後置條件限制總分介於0與滿分之間。若有違反即觸發,指出錯誤最可能的源頭,避免錯誤靜默傳遞。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: C-Q2, B-Q6, D-Q8
A-Q19: 何時應關閉或調降 TRACE/ASSERT?
- A簡: 正式發佈與使用者環境;例外為受控除錯或必要監控時。
- A詳: 一般在發佈版關閉ASSERT並調降TRACE等級,以免中止程式或洩漏內部資訊。必要時可保留關鍵運維追蹤(錯誤級),並在受控情境(特製版、旗標開關)開啟更高粒度以協助重現與診斷。建置與設定管理是關鍵。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q13, C-Q3, D-Q9
A-Q20: 如何撰寫有價值的 ASSERT 訊息?
- A簡: 說明預期與實際、關鍵參數與上下文,便於現場即判斷。
- A詳: 斷言訊息應包含:條件語意(預期與實際值)、關聯鍵值或索引、輸入摘要、當前狀態或步驟。避免空泛訊息。讓任何看到訊息的人(包含未寫該程式者)能迅速縮小範圍並重現問題。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q6, B-Q16, D-Q8
Q&A 類別 B: 技術原理類
B-Q1: .NET 中 Debug/Trace 如何透過條件編譯運作?
- A簡: 以Conditional屬性綁定DEBUG/TRACE符號,決定呼叫是否被編譯進去。
- A詳: System.Diagnostics.Debug與Trace的方法以[Conditional(“DEBUG”)]與[Conditional(“TRACE”)]修飾。當對應符號未定義時,呼叫會在編譯期被移除(不產生IL)。專案建置設定可控制符號,達到同碼兩版的目的。核心組件:條件屬性、建置設定、呼叫端。流程:設定符號→編譯→有無內嵌呼叫。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q9, C-Q3, D-Q3
B-Q2: ASSERT 失敗時背後機制是什麼?
- A簡: 評估條件為假即通知Listener,可能觸發中斷、寫入輸出或顯示對話框。
- A詳: 斷言評估布林條件,為假時呼叫底層通知機制:在IDE中常觸發中斷;於存在DefaultTraceListener時可能顯示對話框;亦可寫入輸出與檔案。核心組件:斷言API、TraceListener、偵錯器。流程:評估→通知→Listener決定行為→開發者介入修正。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q2, B-Q3, D-Q2
B-Q3: Trace 的輸出管線與 TraceListener 機制如何運作?
- A簡: 多個Listener接收訊息並輸出至不同媒介,可依層級與來源過濾。
- A詳: Trace 透過可插拔的Listener(如Default、TextWriter、EventLog、Console)接收訊息並輸出。可設定開關、層級與來源過濾。關鍵步驟:註冊Listener→設定層級→執行Trace→Listener寫出。核心組件:TraceSource、Switch、Listener。可於組態或程式動態配置。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q1, C-Q4, D-Q3
B-Q4: 同碼兩版(DEBUG/RELEASE)建置流程如何設計?
- A簡: 以建置組態、前處理符號與設定檔,產生除錯與發佈兩種變體。
- A詳: 設計步驟:建立Debug/Release組態→在Debug開啟DEBUG/TRACE與驗算→於Release關閉ASSERT或調降TRACE→使用設定檔控制Listener→CI/CD分發相對應產物。核心組件:建置組態、符號、組態檔、CI腳本。確保正式包不含斷言介面副作用。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q3, C-Q3, D-Q1
B-Q5: 函式入口、出口與中間檢查的設計原理?
- A簡: 前置保證輸入正確,中繼維持不變量,出口保證結果符合契約。
- A詳: 契約導向原理:入口用ASSERT確保參數非空、關聯結構一致;中途在狀態轉換後檢查不變量與邊界;出口用ASSERT檢查結果範圍與關聯性。流程:定義契約→標記斷言點→執行並監控→失敗即修正。核心組件:斷言、資料模型、邏輯邊界。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q6, C-Q1, C-Q2
B-Q6: 算分範例的執行流程與斷言點是什麼?
- A簡: 檢查參數與子節點一致→計分→回傳前檢查範圍,任何違反即警示。
- A詳: 流程:入口檢查XmlElement非空與item數一致→處理作答為空的捷徑→讀取配分→以迴圈比對正確與作答→累積分數→出口ASSERT檢查分數在0與滿分之間。核心組件:XPath節點、迴圈、累積器、入口/出口斷言。關鍵步驟將錯誤就地攔截。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q18, C-Q2, D-Q8
B-Q7: 驗算機制在技術上如何實作?
- A簡: 以兩套實作計算同輸入,DEBUG版比對差異並以ASSERT報告。
- A詳: 核心步驟:撰寫安全版(清晰正確)與最佳化版(高效)→於DEBUG建置在關鍵路徑同時呼叫→比對結果→不一致時ASSERT。核心組件:策略介面、雙實作、條件編譯或旗標、比對器。可將驗算包成輔助方法,以便覆用與集中開關。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q14, C-Q5, D-Q8
B-Q8: 黑箱測試如何藉由 ASSERT 捕捉白箱錯誤?
- A簡: 測試僅餵輸入,內部ASSERT在異常狀態即報,無需知內部細節。
- A詳: 黑箱測試不觀察內部,但若程式內布滿斷言,當不變量或契約被破壞時會立即失敗,等同白箱觀測點。流程:測試輸入→運行→斷言監控→失敗回報。核心組件:測試案例、斷言、追蹤訊息。能以最小測試成本捕獲深層邏輯錯誤。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q11, A-Q15, C-Q7
B-Q9: 使用 TRACE 收集故障現場資訊的流程?
- A簡: 事前佈建追蹤→設定層級→重現→收集與關聯→分析修復。
- A詳: 具體:在關鍵路徑加入Trace(含關鍵參數、路徑、識別ID)→於環境設定Trace等級與Listener→請使用者重現→收集輸出檔並關聯請求→分析堆疊與狀態→修復並擴充測試。核心組件:TraceSource、Listener、關聯ID、收斂流程。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q16, C-Q8, D-Q9
B-Q10: ASSERT 與例外處理的搭配原理?
- A簡: 例外面向外部錯誤回應;ASSERT面向內部假設偵錯,兩者互補。
- A詳: 遇外部可預期錯誤(I/O、驗證失敗)應擲出例外並攔截處理;內部契約違反則用ASSERT在開發期爆炸。流程:先以ASSERT鎖定不可發生之事;對可發生之錯誤以例外回覆;於Release可降級為記錄與容錯。核心:區分責任邊界。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q5, D-Q7, C-Q1
B-Q11: 如何避免 TRACE 導致效能負擔?
- A簡: 使用等級過濾、延遲格式化、條件編譯與抽樣,減少無謂開銷。
- A詳: 以TraceSwitch控制輸出層級;在應輸出前先判斷層級避免字串拼接;昂貴訊息以Lazy評估;大量路徑採抽樣或節流;正式環境降級為警告/錯誤。核心:等級、開關、延遲計算、輸出管線。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q9, D-Q5, B-Q3
B-Q12: 多執行緒環境下 TRACE/ASSERT 有何考量?
- A簡: 訊息交錯、排序與關聯性;需加上執行緒ID與活動ID以便追蹤。
- A詳: 並行會造成輸出交錯難讀。建議在Trace中加入ThreadId/TaskId與CorrelationId,必要時使用結構化輸出(JSON)。ASSERT失敗時記錄當前執行緒上下文。核心組件:ID產生、非同步安全Listener、時序排序策略。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: C-Q10, D-Q6, B-Q9
B-Q13: 如何配置 TraceSource 與開關架構?
- A簡: 以命名TraceSource、Switch與Listener組合,依模組分流與控管。
- A詳: 針對每個子系統建立命名TraceSource,綁定SourceSwitch控制層級,註冊多個Listener輸出至不同媒介。可在組態檔調整層級與目的地,達到不改碼即調整的彈性。核心:TraceSource、Switch、Listener、組態。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q4, B-Q3, D-Q3
B-Q14: 如何把 ASSERT 失敗資訊整合到CI/CD?
- A簡: 自動化測試運行收集輸出,失敗時標記構建失敗並上傳工件。
- A詳: 在CI中執行測試與靜態驗證,啟用Trace輸出到檔案;若ASSERT觸發,讓流程失敗並上傳log與迷你轉儲(如可)。核心:測試執行器、log收集、失敗工件、通知管道。可加閾值與趨勢分析。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q8, A-Q16, D-Q9
B-Q15: 在 Release 中保留哪些輕量守門機制?
- A簡: 降級為錯誤級追蹤、關鍵輸入驗證與必要容錯,不使用中斷式ASSERT。
- A詳: 正式環境避免斷言中止,但可保留輸入驗證、非法狀態的錯誤級紀錄與安全降級行為。利用配置開關臨時提高追蹤層級,便於緊急診斷。核心:最少可觀測性、非侵入、可開關。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q19, D-Q9, C-Q3
B-Q16: 斷言訊息的可診斷性設計原則?
- A簡: 清楚呈現條件語意、期望/實際、關鍵識別與建議動作。
- A詳: 訊息模板包含:描述性標題、期望值vs實際值、相關鍵(ID/索引)、上下文摘要、可能影響、建議追查步驟或連結。目的在第一時間賦能修復者。核心:可讀性、可行性、上下文。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q20, C-Q6, D-Q8
B-Q17: 如何決定斷言的粒度與頻率?
- A簡: 針對高風險、高複雜、難重現與邊界條件處加強;避免重複低價值檢查。
- A詳: 依風險矩陣與複雜度評估:資料邊界、狀態機轉換、並行臨界區域、演算法臨界值。避免在熱路徑過密儘管Debug可接受。隨著測試覆蓋提升可適度調整。核心:風險驅動、邊際報酬、性能考量。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q11, A-Q7, D-Q5
B-Q18: ASSERT 與「設計 by 契約」的對應?
- A簡: ASSERT是契約的可執行化實踐,將要求與保證編碼並自動驗證。
- A詳: 設計by契約強調前置、後置、不變條件的明確約定。ASSERT把這些約定可執行化,於運行中自動檢查並失敗即示警。也可輔以註解與文件,形成多層次的契約與驗證體系。核心:契約明文化、運行檢查、自動化。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q6, B-Q5, C-Q1
B-Q19: 追蹤等級(Verbose/Info/Warning/Error)如何運用?
- A簡: 依重要性分流輸出;除錯細節用Verbose,正式多用Warning/Error。
- A詳: Verbose記錄細節、流程與參數;Info記錄關鍵里程碑;Warning記錄異常但可容錯狀態;Error記錄失敗與需介入事件。依環境設定門檻,在Debug放寬,在Release收斂。核心:等級策略、環境門檻、Listener過濾。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q8, B-Q3, C-Q4
B-Q20: 如何把Trace輸出導至IDE、檔案與事件檢視器?
- A簡: 註冊對應Listener,或透過組態指定目的地與層級過濾。
- A詳: 以DefaultTraceListener輸出至IDE;TextWriterTraceListener輸出檔案;EventLogTraceListener寫事件檢視器。可於app.config設定sources、switches與listeners,或程式動態新增。流程:新增Listener→設級別→驗證輸出。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: C-Q4, D-Q3, B-Q13
Q&A 類別 C: 實作應用類(10題)
C-Q1: 如何在C#函式入口加上前置條件ASSERT?
- A簡: 使用Debug/Trace.Assert檢查非空與範圍,違反即中斷或輸出警示。
- A詳:
- 步驟: 引入System.Diagnostics→在入口檢查參數→加入具體訊息。
- 程式碼: using System.Diagnostics; void Foo(string id, int count){ Debug.Assert(id != null, “id不可為null”); Debug.Assert(count >= 0 && count <= 100, $”count超界:{count}”); }
- 注意: 用Debug.Assert避免進正式版;範圍錯誤屬輸入驗證時仍需例外處理。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q6, B-Q10, A-Q9
C-Q2: 如何為算分範例加入入口/出口斷言?
- A簡: 入口驗證XmlElement非空與子節點一致;出口確保分數在0至滿分。
- A詳:
- 步驟: 入口三斷言→計分→出口兩斷言。
- 程式碼: int Compute(XmlElement q, XmlElement p){ Debug.Assert(q!=null && p!=null,”題目或作答為null”); var qi=q.SelectNodes(“item”); var pi=p.SelectNodes(“item”); Debug.Assert(qi.Count==pi.Count,$”項數不一致:{qi.Count}!={pi.Count}”); int quizScore=int.Parse(q.GetAttribute(“score”)); int total=…; //計分 Debug.Assert(total>=0 && total<=quizScore,$”分數越界:{total}”); return total; }
- 注意: 使用具體訊息,Release降級為記錄或移除。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q18, B-Q6, D-Q8
C-Q3: 如何設定VS建置讓Release關閉ASSERT/降噪TRACE?
- A簡: 在組態關閉DEBUG/TRACE符號或調低等級,確保發佈版不彈ASSERT。
- A詳:
- 步驟: 專案屬性→建置→取消定義DEBUG(與視需求TRACE)→調整app.config之TraceSource層級→CI中驗證。
- 設定:
與 設定lvl=Warning/Error。 - 注意: 預設Release常仍定義TRACE,視情境關閉或降級;保留必要營運日誌。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q1, B-Q4, A-Q13
C-Q4: 如何自訂TraceListener把TRACE寫入檔案?
- A簡: 新增TextWriterTraceListener並設定AutoFlush與檔案路徑,註冊至Trace。
- A詳:
- 步驟: 建立檔案串流→加入Listener→設定層級→輸出。
- 程式碼: var fs=File.CreateText(“diag.log”); var l=new TextWriterTraceListener(fs){TraceOutputOptions=TraceOptions.DateTime}; Trace.Listeners.Add(l); Trace.AutoFlush=true; Trace.WriteLine(“start”);
- 注意: 檔案鎖定、輪替與大小限制;正式環境注意敏感資訊。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q3, B-Q20, D-Q4
C-Q5: 如何在DEBUG版啟用「驗算」雙算法比對?
- A簡: 用條件編譯同時呼叫安全版與最佳化版,差異即ASSERT失敗。
- A詳:
- 步驟: 實作 ISolver Safe/Opt→在DEBUG呼叫兩者→Compare→ASSERT。
- 程式碼: #if DEBUG var a=safe.Solve(x); var b=opt.Solve(x); Debug.Assert(Equals(a,b),$”驗算失敗 a:{a} b:{b}”); return b; #else return opt.Solve(x); #endif
- 注意: 比對容忍度(浮點)、昂貴路徑避免在Release執行。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q14, B-Q7, D-Q8
C-Q6: 如何撰寫富含脈絡的ASSERT訊息?
- A簡: 內含期望/實際、關鍵索引或ID、輸入摘要與建議動作。
- A詳:
- 步驟: 設計訊息模板→封裝Helper→統一使用。
- 程式碼: void Must(bool cond,string exp,object ctx){ Debug.Assert(cond,$”[ASSERT]{exp} ctx:{Json(ctx)}”); }
- 注意: 避免大量序列化開銷;敏感資料遮罩;訊息可直連故障工單。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q20, B-Q16, D-Q8
C-Q7: 如何在單元測試中使用Assert重現故障?
- A簡: 撰寫覆蓋邊界條件的測試案例,讓程式內的ASSERT於測試時觸發。
- A詳:
- 步驟: 分析故障→撰寫最小重現輸入→執行測試→觀察ASSERT與TRACE。
- 程式碼(xUnit):
[Fact] void Score_OutOfRange(){
var q=…; var p=…; //製造越界
Assert.ThrowsAny
(()=>Compute(q,p)); } - 注意: 某些測試Runner不會中斷於Debug.Assert;可改以例外或旗標設計。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q15, B-Q8, D-Q8
C-Q8: 如何把TRACE整合進CI產物便於遠端診斷?
- A簡: 測試與打包流程產出log檔與崩潰資訊,失敗時自動上傳。
- A詳:
- 步驟: CI腳本啟用Trace→保存diag.log→失敗時作為artifact→通知附連結。
- 設定: pipeline.yml中配置收集路徑與保留天數。
- 注意: 秘密管理、日誌清洗與容量控管;GDPR/隱私合規。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q14, A-Q16, D-Q9
C-Q9: 如何用條件編譯包覆昂貴的檢查與輸出?
- A簡: 將昂貴驗證與Verbose輸出置於#if DEBUG區塊或以Switch判斷再執行。
- A詳:
- 步驟: 判定昂貴處→以#if DEBUG包覆或先檢查switch.IsEnabled→才格式化輸出。
- 程式碼: if(ts.Switch.ShouldTrace(TraceEventType.Verbose)) Trace.TraceInformation($”{Expensive()}”);
- 注意: 避免字串拼接副作用;抽象為Helper提升一致性。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q11, B-Q19, D-Q5
C-Q10: 如何在多執行緒追蹤中加入關聯ID?
- A簡: 為請求產生CorrelationId,於TRACE附帶,並在子工作傳遞。
- A詳:
- 步驟: 進入點產生Guid→存入AsyncLocal→Trace輸出包含Id→子工作沿用。
- 程式碼:
static readonly AsyncLocal
Cid=new(); void Start(){ Cid.Value=Guid.NewGuid().ToString(); } void Log(string m){ Trace.WriteLine($"[{Cid.Value}] {m}"); } - 注意: 非同步流程傳遞;跨程序需外帶(Header/消息)。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q12, D-Q6, B-Q9
Q&A 類別 D: 問題解決類(10題)
D-Q1: 使用者環境冒出ASSERT視窗怎麼辦?
- A簡: 先改用不含ASSERT的Release版;必要時用受控debug版協助重現。
- A詳:
- 症狀: 正式環境彈出ASSERT或中止。
- 原因: 發佈包未關閉ASSERT、Listener顯示對話框。
- 解法: 重新發佈關閉DEBUG/ASSERT;以設定關閉對話框;提供特製除錯版協助重現。
- 預防: 建置檢查清單與CI守門;發佈前煙霧測試。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q13, B-Q4, C-Q3
D-Q2: ASSERT失敗未自動中斷到程式碼?
- A簡: 啟用JIT Debugger或附加偵錯器,確認Listener與IDE設定。
- A詳:
- 症狀: ASSERT訊息出現但未跳至原始碼。
- 原因: 未附加偵錯器、JIT禁用、Symbols未載入。
- 解法: 啟用JIT、附加至程序、載入PDB;確認DefaultTraceListener行為。
- 預防: 調校團隊IDE設定與偵錯腳本。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q2, B-Q20, C-Q7
D-Q3: TRACE訊息沒出現在輸出視窗或檔案?
- A簡: 檢查Listener註冊、層級開關與組態路徑權限是否正確。
- A詳:
- 症狀: 無追蹤輸出。
- 原因: 未註冊Listener、層級過高被過濾、檔案權限不足。
- 解法: 新增對應Listener、調整Switch與等級、確認檔案路徑與ACL。
- 預防: 設計自檢與啟動時輸出健康檢查行。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q3, B-Q20, C-Q4
D-Q4: Log檔未寫入或被覆寫怎麼辦?
- A簡: 啟用AutoFlush、檢查共享模式與輪替策略,避免單檔無限增長。
- A詳:
- 症狀: 檔案無內容或內容消失。
- 原因: Flush晚、被另一進程鎖定、覆蓋開檔、無輪替。
- 解法: 設AutoFlush/定時Flush、以Append模式、加檔案輪替與上限、避免多重實例。
- 預防: 日誌框架化管理與測試。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q4, B-Q11, B-Q20
D-Q5: 大量TRACE造成效能下降?
- A簡: 調低等級、延遲格式化與抽樣記錄,避免熱路徑過度輸出。
- A詳:
- 症狀: 延遲增大、CPU/IO飆高。
- 原因: 過多字串拼接與同步IO寫檔。
- 解法: 降級到Warning/Error、ShouldTrace前先判斷、批次或非同步寫、抽樣。
- 預防: 效能預算與壓測把關。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q11, C-Q9, B-Q19
D-Q6: 多執行緒下TRACE訊息交錯難讀?
- A簡: 加入ThreadId與CorrelationId,必要時使用結構化輸出與排序工具。
- A詳:
- 症狀: 訊息順序混亂,難以重建流程。
- 原因: 多線程並發輸出交錯。
- 解法: 附帶執行緒/活動ID、採JSON輸出、用後處理工具按ID/時間排序。
- 預防: 自動注入ID的記錄介面。
- 難度: 高級
- 學習階段: 進階
- 關聯概念: B-Q12, C-Q10, B-Q9
D-Q7: ASSERT常因參數為null觸發,如何處理?
- A簡: 區分BUG與輸入錯誤;入口加驗證,對外部輸入拋例外並提示。
- A詳:
- 症狀: 函式入口斷言頻繁失敗。
- 原因: 上游未遵守API契約或使用者輸入缺失。
- 解法: 內部呼叫違約屬BUG→修上游;外部輸入→改為驗證與例外;擴充單元測試覆蓋。
- 預防: 明確文件與型別系統(可空性)約束。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q5, B-Q10, C-Q1
D-Q8: 負分BUG仍漏網,如何診斷?
- A簡: 加強後置條件ASSERT、補齊邊界測試與驗算雙算法比對。
- A詳:
- 症狀: 最終分數小於0或大於滿分。
- 原因: 算法加減錯誤、索引錯置、配分不一致。
- 解法: 在回傳前ASSERT範圍;加入黑箱邊界測試;DEBUG啟用驗算;TRACE記錄加總過程。
- 預防: 需求測試案例化、審查邊界條件。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q18, B-Q6, C-Q5
D-Q9: Release缺乏TRACE導致難以追查?
- A簡: 臨時提高追蹤層級或提供受控debug版,收集必要現場資料。
- A詳:
- 症狀: 使用者報錯但無足夠log。
- 原因: 正式環境降噪過度。
- 解法: 以設定開關提高層級至Warning/Info;短期部署含TRACE的特製版;要求重現並收集檔案。
- 預防: 保留最少可觀測性,預先設計開關與工件收集。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q16, B-Q15, C-Q8
D-Q10: 單元測試全過但實際ASSERT仍觸發?
- A簡: 測試覆蓋不足或情境不同;補充邊界與組合,並收集現場TRACE。
- A詳:
- 症狀: CI全綠,實際執行觸發斷言。
- 原因: 測試資料分布不匹配、環境差異、並行條件未覆蓋。
- 解法: 擴充屬性測試/隨機測試、加入並行與時序情境、以現場TRACE反推案例。
- 預防: 風險導向測試設計與覆蓋監測。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q15, B-Q8, B-Q12
學習路徑索引
- 初學者:建議先學習哪 15 題
- A-Q1: 什麼是 TRACE?
- A-Q2: 什麼是 ASSERT?
- A-Q3: 為什麼需要同時維護 DEBUG/RELEASE 兩個版本?
- A-Q4: 使用 TRACE/ASSERT 的核心價值是什麼?
- A-Q5: ASSERT 與執行期錯誤處理有何差異?
- A-Q7: 在哪裡最應該加上 ASSERT?
- A-Q10: 為什麼 TRACE 輸出要與一般輸出分離?
- A-Q11: 使用 ASSERT 會拖慢開發嗎?
- A-Q12: 為何 ASSERT 不該用來處理使用者輸入錯誤?
- A-Q18: 在算分範例中,ASSERT 的角色是什麼?
- B-Q6: 算分範例的執行流程與斷言點是什麼?
- C-Q1: 如何在C#函式入口加上前置條件ASSERT?
- C-Q2: 如何為算分範例加入入口/出口斷言?
- D-Q1: 使用者環境冒出ASSERT視窗怎麼辦?
- D-Q8: 負分BUG仍漏網,如何診斷?
- 中級者:建議學習哪 20 題
- A-Q6: 什麼是前置、後置與不變條件(設計合約)?
- A-Q8: TRACE 與一般應用層日誌有何差異?
- A-Q9: Debug.Assert 與 Trace.Assert 的差異是什麼(.NET)?
- A-Q14: 什麼是「驗算」策略(安全版與最佳化版比對)?
- A-Q15: ASSERT 與單元測試的關係?
- A-Q16: TRACE/ASSERT 如何協助處理難以重現的BUG?
- A-Q19: 何時應關閉或調降 TRACE/ASSERT?
- A-Q20: 如何撰寫有價值的 ASSERT 訊息?
- B-Q1: .NET 中 Debug/Trace 如何透過條件編譯運作?
- B-Q2: ASSERT 失敗時背後機制是什麼?
- B-Q3: Trace 的輸出管線與 TraceListener 機制如何運作?
- B-Q4: 同碼兩版(DEBUG/RELEASE)建置流程如何設計?
- B-Q10: ASSERT 與例外處理的搭配原理?
- B-Q11: 如何避免 TRACE 導致效能負擔?
- B-Q13: 如何配置 TraceSource 與開關架構?
- B-Q19: 追蹤等級(Verbose/Info/Warning/Error)如何運用?
- B-Q20: 如何把Trace輸出導至IDE、檔案與事件檢視器?
- C-Q3: 如何設定VS建置讓Release關閉ASSERT/降噪TRACE?
- C-Q4: 如何自訂TraceListener把TRACE寫入檔案?
- C-Q6: 如何撰寫富含脈絡的ASSERT訊息?
- 高級者:建議關注哪 15 題
- B-Q5: 函式入口、出口與中間檢查的設計原理?
- B-Q7: 驗算機制在技術上如何實作?
- B-Q8: 黑箱測試如何藉由 ASSERT 捕捉白箱錯誤?
- B-Q12: 多執行緒環境下 TRACE/ASSERT 有何考量?
- B-Q14: 如何把 ASSERT 失敗資訊整合到CI/CD?
- B-Q15: 在 Release 中保留哪些輕量守門機制?
- B-Q16: 斷言訊息的可診斷性設計原則?
- B-Q17: 如何決定斷言的粒度與頻率?
- B-Q18: ASSERT 與「設計 by 契約」的對應?
- C-Q5: 如何在DEBUG版啟用「驗算」雙算法比對?
- C-Q8: 如何把TRACE整合進CI產物便於遠端診斷?
- C-Q9: 如何用條件編譯包覆昂貴的檢查與輸出?
- C-Q10: 如何在多執行緒追蹤中加入關聯ID?
- D-Q5: 大量TRACE造成效能下降?
- D-Q10: 單元測試全過但實際ASSERT仍觸發?