<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Quality on Tarragon</title><link>https://tarrragon.github.io/blog/tags/quality/</link><description>Recent content in Quality on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/quality/index.xml" rel="self" type="application/rss+xml"/><item><title>Assertion 品質三問</title><link>https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/05-test-design-judgment/assertion-quality/</guid><description>&lt;p>Assertion 是 test 的結論 — 「我認為程式碼的行為應該是 X」。Assertion 的品質決定了 test 的有效性：無效的 assertion 讓 test 通過但問題仍在，或讓 test 隨機失敗但問題不在程式碼。&lt;/p>
&lt;h2 id="三個判斷問題">三個判斷問題&lt;/h2>
&lt;h3 id="斷言的是行為嗎">斷言的是行為嗎&lt;/h3>
&lt;p>Assertion 應該斷言程式碼的外部可觀察行為（回傳值、狀態變化、副作用），而非內部實作細節（私有變數的值、呼叫次數、執行順序）。&lt;/p>
&lt;p>斷言行為的 test 在重構時不需要改 — 只要行為不變，test 就通過。斷言實作的 test 在任何內部調整時都會壞掉，即使行為完全正確。&lt;/p>
&lt;p>例：驗證「parser 正確解析紅色文字」時，斷言 token 的顏色屬性（行為）比斷言 parser 內部的 state machine 走了哪些步驟（實作）更穩定。&lt;/p>
&lt;h3 id="能區分正確和錯誤嗎">能區分正確和錯誤嗎&lt;/h3>
&lt;p>Assertion 應該在程式碼正確時通過、錯誤時失敗。如果 assertion 無論程式碼正確或錯誤都通過，這個 assertion 沒有提供保護。&lt;/p>
&lt;p>常見的無效 assertion：&lt;/p>
&lt;p>&lt;strong>斷言不為 null&lt;/strong>：&lt;code>expect(result, isNotNull)&lt;/code> 只驗證「有回傳值」，不驗證「回傳值正確」。回傳錯誤的值也會通過。&lt;/p>
&lt;p>&lt;strong>斷言型別&lt;/strong>：&lt;code>expect(result, isA&amp;lt;List&amp;gt;())&lt;/code> 只驗證「回傳 List」，不驗證 List 的內容。空 List 和錯誤內容的 List 都會通過。&lt;/p>
&lt;p>&lt;strong>斷言包含&lt;/strong>：&lt;code>expect(result, contains('error'))&lt;/code> 驗證字串包含 &amp;rsquo;error&amp;rsquo;，但如果回傳 &amp;rsquo;no error occurred&amp;rsquo;（正確情境）也包含 &amp;rsquo;error&amp;rsquo; — assertion 無法區分正確和錯誤。&lt;/p>
&lt;p>T.C3 的 parser test 斷言 &lt;code>expect(tokens.first, isA&amp;lt;TextToken&amp;gt;())&lt;/code> — 驗證 token 型別是 TextToken。但正確解析和透傳亂碼都可能產生 TextToken，assertion 無法區分（本章合成，TF-5 Derive — 透傳的靜默副作用和 assertion 的區分力有 tension）。&lt;/p>
&lt;h3 id="會-flaky-嗎">會 flaky 嗎&lt;/h3>
&lt;p>Assertion 是否依賴非確定性因素 — 時間、隨機數、外部服務狀態、執行順序。如果是，test 可能在程式碼正確時失敗（false negative），降低團隊對 test 的信任。&lt;/p>
&lt;p>常見的 flaky assertion 來源：&lt;/p>
&lt;ul>
&lt;li>依賴 &lt;code>DateTime.now()&lt;/code> 或 &lt;code>stopwatch.elapsed&lt;/code> — 時間精度和系統負載影響結果&lt;/li>
&lt;li>依賴特定的執行順序 — &lt;code>Set&lt;/code> 或 &lt;code>Map&lt;/code> 的迭代順序不保證&lt;/li>
&lt;li>依賴外部服務的回應時間 — 網路延遲導致 timeout&lt;/li>
&lt;/ul>
&lt;h2 id="assertion-改善的操作步驟">Assertion 改善的操作步驟&lt;/h2>
&lt;p>對既有的 test assertion 逐一問三個問題，標記需要改善的：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>行為 check&lt;/strong>：assertion 斷言的是 public API 的回傳值或狀態嗎？如果斷言私有變數或呼叫次數，考慮改成行為斷言。&lt;/li>
&lt;li>&lt;strong>區分 check&lt;/strong>：把 assertion 改成反向（&lt;code>expect(result, 'wrong_value')&lt;/code>），test 會失敗嗎？如果 assertion 太寬鬆（isNotNull、isA），test 可能在錯誤的情況下也通過。&lt;/li>
&lt;li>&lt;strong>穩定 check&lt;/strong>：連續跑 10 次，每次都通過嗎？如果有 flaky，找到依賴的非確定性因素。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>Flaky test 的系統性根因分類 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/flaky-test-root-cause/" data-link-title="Flaky test 根因分類" data-link-desc="計時依賴 / 環境差異 / 資源競爭 / 非確定性輸出 — 四類 flaky test 根因的辨識和處理策略">Flaky test 根因分類&lt;/a>&lt;/li>
&lt;li>測試資料的代表性 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性&lt;/a>&lt;/li>
&lt;li>Mock 邊界判斷 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Assertion 是 test 的結論 — 「我認為程式碼的行為應該是 X」。Assertion 的品質決定了 test 的有效性：無效的 assertion 讓 test 通過但問題仍在，或讓 test 隨機失敗但問題不在程式碼。</p>
<h2 id="三個判斷問題">三個判斷問題</h2>
<h3 id="斷言的是行為嗎">斷言的是行為嗎</h3>
<p>Assertion 應該斷言程式碼的外部可觀察行為（回傳值、狀態變化、副作用），而非內部實作細節（私有變數的值、呼叫次數、執行順序）。</p>
<p>斷言行為的 test 在重構時不需要改 — 只要行為不變，test 就通過。斷言實作的 test 在任何內部調整時都會壞掉，即使行為完全正確。</p>
<p>例：驗證「parser 正確解析紅色文字」時，斷言 token 的顏色屬性（行為）比斷言 parser 內部的 state machine 走了哪些步驟（實作）更穩定。</p>
<h3 id="能區分正確和錯誤嗎">能區分正確和錯誤嗎</h3>
<p>Assertion 應該在程式碼正確時通過、錯誤時失敗。如果 assertion 無論程式碼正確或錯誤都通過，這個 assertion 沒有提供保護。</p>
<p>常見的無效 assertion：</p>
<p><strong>斷言不為 null</strong>：<code>expect(result, isNotNull)</code> 只驗證「有回傳值」，不驗證「回傳值正確」。回傳錯誤的值也會通過。</p>
<p><strong>斷言型別</strong>：<code>expect(result, isA&lt;List&gt;())</code> 只驗證「回傳 List」，不驗證 List 的內容。空 List 和錯誤內容的 List 都會通過。</p>
<p><strong>斷言包含</strong>：<code>expect(result, contains('error'))</code> 驗證字串包含 &rsquo;error&rsquo;，但如果回傳 &rsquo;no error occurred&rsquo;（正確情境）也包含 &rsquo;error&rsquo; — assertion 無法區分正確和錯誤。</p>
<p>T.C3 的 parser test 斷言 <code>expect(tokens.first, isA&lt;TextToken&gt;())</code> — 驗證 token 型別是 TextToken。但正確解析和透傳亂碼都可能產生 TextToken，assertion 無法區分（本章合成，TF-5 Derive — 透傳的靜默副作用和 assertion 的區分力有 tension）。</p>
<h3 id="會-flaky-嗎">會 flaky 嗎</h3>
<p>Assertion 是否依賴非確定性因素 — 時間、隨機數、外部服務狀態、執行順序。如果是，test 可能在程式碼正確時失敗（false negative），降低團隊對 test 的信任。</p>
<p>常見的 flaky assertion 來源：</p>
<ul>
<li>依賴 <code>DateTime.now()</code> 或 <code>stopwatch.elapsed</code> — 時間精度和系統負載影響結果</li>
<li>依賴特定的執行順序 — <code>Set</code> 或 <code>Map</code> 的迭代順序不保證</li>
<li>依賴外部服務的回應時間 — 網路延遲導致 timeout</li>
</ul>
<h2 id="assertion-改善的操作步驟">Assertion 改善的操作步驟</h2>
<p>對既有的 test assertion 逐一問三個問題，標記需要改善的：</p>
<ol>
<li><strong>行為 check</strong>：assertion 斷言的是 public API 的回傳值或狀態嗎？如果斷言私有變數或呼叫次數，考慮改成行為斷言。</li>
<li><strong>區分 check</strong>：把 assertion 改成反向（<code>expect(result, 'wrong_value')</code>），test 會失敗嗎？如果 assertion 太寬鬆（isNotNull、isA），test 可能在錯誤的情況下也通過。</li>
<li><strong>穩定 check</strong>：連續跑 10 次，每次都通過嗎？如果有 flaky，找到依賴的非確定性因素。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Flaky test 的系統性根因分類 → <a href="/blog/testing/05-test-design-judgment/flaky-test-root-cause/" data-link-title="Flaky test 根因分類" data-link-desc="計時依賴 / 環境差異 / 資源競爭 / 非確定性輸出 — 四類 flaky test 根因的辨識和處理策略">Flaky test 根因分類</a></li>
<li>測試資料的代表性 → <a href="/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性</a></li>
<li>Mock 邊界判斷 → <a href="/blog/testing/05-test-design-judgment/mock-boundary-decision/" data-link-title="Mock 邊界判斷決策表" data-link-desc="什麼時候 mock 夠用、什麼時候需要真實服務 — 從 API 層 / 協議層 / 環境層的斷裂點判斷 mock 的適用範圍">Mock 邊界判斷決策表</a></li>
</ul>
]]></content:encoded></item><item><title>「事後補 log」vs「設計產物 log」的品質差異</title><link>https://tarrragon.github.io/blog/testing/02-client-observability/hotfix-log-vs-designed-log/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/02-client-observability/hotfix-log-vs-designed-log/</guid><description>&lt;p>事後補 log 和設計產物 log 的差別在於產出時機和品質標準。事後補的 log 在 debug 壓力下產出，目的是「讓這次的問題能被定位」；設計產物的 log 在功能規格階段產出，目的是「讓未來任何問題都能被定位」。兩者的品質差異在格式統一性、覆蓋完整性和長期維護成本三個面向上表現明顯。&lt;/p>
&lt;h2 id="格式統一性">格式統一性&lt;/h2>
&lt;p>app_tunnel 在 W2 修復時補的 &lt;code>developer.log&lt;/code> 格式不統一（&lt;a href="https://tarrragon.github.io/blog/testing/cases/client-log-absent-debug-cost/" data-link-title="T.C4 Client-side log 缺失導致 debug 只能靠實機盲測" data-link-desc="Flutter app 六個核心元件中只有兩個有 log（且全是 W2 hotfix 補的），連線失敗時開發者無法從任何 log 判斷失敗發生在哪一步 — 被迫用最昂貴的 debug 方式：插拔裝置反覆測試">T.C4&lt;/a>）。不同元件由不同時間點、不同 debug 需求補上的 log，各自有各自的風格：&lt;/p>
&lt;p>有的帶 &lt;code>name:&lt;/code> 參數讓 log 可以按元件過濾：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">developer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;WS connected&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">name:&lt;/span> &lt;span class="s1">&amp;#39;ConnectionManager&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>有的不帶，混在全域 log 裡無法過濾：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">developer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;auth token sent&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>有的帶 &lt;code>// i18n-exempt&lt;/code> 標記（因為 linter 會對 hardcoded string 報警），有的忘了加。有的把錯誤訊息放在 &lt;code>error:&lt;/code> 參數，有的用字串串接。&lt;/p>
&lt;p>這些不一致來自事後補 log 的結構性原因：每條 log 是在解決當下問題時加的，沒有統一規範，也沒有 review。加完能定位問題就提交，下次遇到新問題再加新的 log — 格式隨機。&lt;/p>
&lt;p>設計產物 log 在產出前就有命名規則和格式規範（見 &lt;a href="https://tarrragon.github.io/blog/testing/02-client-observability/log-point-in-spec/" data-link-title="功能規格中的 log 點定義方法" data-link-desc="把 log 點設計從 debug 階段前移到功能規格階段 — 每個功能的規格文件新增可觀測性欄位，列出啟動 / 步驟 / 錯誤 / 完成四類 log 點">功能規格中的 log 點定義方法&lt;/a>）。所有 log 點走同一個 &lt;code>AppLogger&lt;/code> 介面，name、level、結構化欄位在規格階段就定義好，實作時照規格寫。&lt;/p>
&lt;h2 id="覆蓋完整性">覆蓋完整性&lt;/h2>
&lt;p>事後補 log 的覆蓋範圍由「哪些問題已經發生過」決定。W2-002 auth token 問題觸發了 &lt;code>ConnectionManager&lt;/code> 和 &lt;code>TerminalScreen&lt;/code> 的 log 補充，但 &lt;code>TtydProtocol&lt;/code>、&lt;code>BiometricService&lt;/code>、&lt;code>CredentialRepository&lt;/code>、&lt;code>EnrollmentScreen&lt;/code> 四個元件仍然零 log — 因為這四個元件在 W2 的 debug 過程中不是瓶頸。&lt;/p>
&lt;p>六個核心元件中四個零 log 的狀態意味著：下次如果問題出在 &lt;code>BiometricService&lt;/code>（例如特定 iOS 版本的 biometric API 行為改變），debug 又會回到「手動加 log → 重新編譯 → 插拔裝置」的循環。事後補 log 只覆蓋已知問題的路徑，對未知問題沒有防護。&lt;/p>
&lt;p>設計產物 log 的覆蓋範圍由功能流程的步驟數決定。每個功能規格列出所有步驟的 log 點，不管這些步驟是否曾經出過問題。&lt;code>BiometricService.authenticate()&lt;/code> 在規格中就有 start/done/failed 三個 log 點，無論是否遇過 biometric 問題。&lt;/p>
&lt;h2 id="維護成本">維護成本&lt;/h2>
&lt;p>事後補 log 隨 debug 過程累積，沒有統一管理。隨時間推移：&lt;/p>
&lt;ul>
&lt;li>某些 log 的觸發條件已經不存在了（被修復的 bug 對應的 log），但沒人清理&lt;/li>
&lt;li>某些 log 的格式和新加的 log 不一致，但沒人統一&lt;/li>
&lt;li>某些 log 的 context 資訊不足（當時能定位問題是因為開發者記得 context，半年後換人接手就不夠了）&lt;/li>
&lt;li>某些 log 在 release build 中不該出現但忘了加條件&lt;/li>
&lt;/ul>
&lt;p>設計產物 log 有規格文件作為 source of truth。功能變更時更新規格中的 log 點列表，刪除的步驟對應的 log 點一起刪除，新增的步驟對應的 log 點一起新增。Log 的生命週期和功能的生命週期綁定。&lt;/p>
&lt;h2 id="從事後補過渡到設計產物">從事後補過渡到設計產物&lt;/h2>
&lt;p>已有的事後補 log 不需要全部重寫。過渡策略是：&lt;/p>
&lt;p>&lt;strong>統一入口&lt;/strong>：建立 &lt;code>AppLogger&lt;/code> 封裝，把現有的 &lt;code>developer.log&lt;/code> 呼叫改為走 &lt;code>AppLogger&lt;/code>。這一步不改 log 內容，只改呼叫方式，讓後續的格式統一和功能切換有統一入口。&lt;/p></description><content:encoded><![CDATA[<p>事後補 log 和設計產物 log 的差別在於產出時機和品質標準。事後補的 log 在 debug 壓力下產出，目的是「讓這次的問題能被定位」；設計產物的 log 在功能規格階段產出，目的是「讓未來任何問題都能被定位」。兩者的品質差異在格式統一性、覆蓋完整性和長期維護成本三個面向上表現明顯。</p>
<h2 id="格式統一性">格式統一性</h2>
<p>app_tunnel 在 W2 修復時補的 <code>developer.log</code> 格式不統一（<a href="/blog/testing/cases/client-log-absent-debug-cost/" data-link-title="T.C4 Client-side log 缺失導致 debug 只能靠實機盲測" data-link-desc="Flutter app 六個核心元件中只有兩個有 log（且全是 W2 hotfix 補的），連線失敗時開發者無法從任何 log 判斷失敗發生在哪一步 — 被迫用最昂貴的 debug 方式：插拔裝置反覆測試">T.C4</a>）。不同元件由不同時間點、不同 debug 需求補上的 log，各自有各自的風格：</p>
<p>有的帶 <code>name:</code> 參數讓 log 可以按元件過濾：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">developer</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="s1">&#39;WS connected&#39;</span><span class="p">,</span> <span class="nl">name:</span> <span class="s1">&#39;ConnectionManager&#39;</span><span class="p">);</span></span></span></code></pre></div><p>有的不帶，混在全域 log 裡無法過濾：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">developer</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="s1">&#39;auth token sent&#39;</span><span class="p">);</span></span></span></code></pre></div><p>有的帶 <code>// i18n-exempt</code> 標記（因為 linter 會對 hardcoded string 報警），有的忘了加。有的把錯誤訊息放在 <code>error:</code> 參數，有的用字串串接。</p>
<p>這些不一致來自事後補 log 的結構性原因：每條 log 是在解決當下問題時加的，沒有統一規範，也沒有 review。加完能定位問題就提交，下次遇到新問題再加新的 log — 格式隨機。</p>
<p>設計產物 log 在產出前就有命名規則和格式規範（見 <a href="/blog/testing/02-client-observability/log-point-in-spec/" data-link-title="功能規格中的 log 點定義方法" data-link-desc="把 log 點設計從 debug 階段前移到功能規格階段 — 每個功能的規格文件新增可觀測性欄位，列出啟動 / 步驟 / 錯誤 / 完成四類 log 點">功能規格中的 log 點定義方法</a>）。所有 log 點走同一個 <code>AppLogger</code> 介面，name、level、結構化欄位在規格階段就定義好，實作時照規格寫。</p>
<h2 id="覆蓋完整性">覆蓋完整性</h2>
<p>事後補 log 的覆蓋範圍由「哪些問題已經發生過」決定。W2-002 auth token 問題觸發了 <code>ConnectionManager</code> 和 <code>TerminalScreen</code> 的 log 補充，但 <code>TtydProtocol</code>、<code>BiometricService</code>、<code>CredentialRepository</code>、<code>EnrollmentScreen</code> 四個元件仍然零 log — 因為這四個元件在 W2 的 debug 過程中不是瓶頸。</p>
<p>六個核心元件中四個零 log 的狀態意味著：下次如果問題出在 <code>BiometricService</code>（例如特定 iOS 版本的 biometric API 行為改變），debug 又會回到「手動加 log → 重新編譯 → 插拔裝置」的循環。事後補 log 只覆蓋已知問題的路徑，對未知問題沒有防護。</p>
<p>設計產物 log 的覆蓋範圍由功能流程的步驟數決定。每個功能規格列出所有步驟的 log 點，不管這些步驟是否曾經出過問題。<code>BiometricService.authenticate()</code> 在規格中就有 start/done/failed 三個 log 點，無論是否遇過 biometric 問題。</p>
<h2 id="維護成本">維護成本</h2>
<p>事後補 log 隨 debug 過程累積，沒有統一管理。隨時間推移：</p>
<ul>
<li>某些 log 的觸發條件已經不存在了（被修復的 bug 對應的 log），但沒人清理</li>
<li>某些 log 的格式和新加的 log 不一致，但沒人統一</li>
<li>某些 log 的 context 資訊不足（當時能定位問題是因為開發者記得 context，半年後換人接手就不夠了）</li>
<li>某些 log 在 release build 中不該出現但忘了加條件</li>
</ul>
<p>設計產物 log 有規格文件作為 source of truth。功能變更時更新規格中的 log 點列表，刪除的步驟對應的 log 點一起刪除，新增的步驟對應的 log 點一起新增。Log 的生命週期和功能的生命週期綁定。</p>
<h2 id="從事後補過渡到設計產物">從事後補過渡到設計產物</h2>
<p>已有的事後補 log 不需要全部重寫。過渡策略是：</p>
<p><strong>統一入口</strong>：建立 <code>AppLogger</code> 封裝，把現有的 <code>developer.log</code> 呼叫改為走 <code>AppLogger</code>。這一步不改 log 內容，只改呼叫方式，讓後續的格式統一和功能切換有統一入口。</p>
<p><strong>補規格</strong>：對每個功能寫出 log 點規格表（四類 log 點），比對現有 log 和規格的差距。規格中有但程式碼中沒有的 log 點 = 覆蓋缺口，補上。程式碼中有但規格中沒有的 log 點 = 可能是過時的 debug log，評估是否刪除。</p>
<p><strong>新功能走設計產物流程</strong>：從下一個新功能開始，功能規格中包含可觀測性欄位。新功能的 log 從一開始就是設計產物品質。</p>
<p>過渡的第一步是建立統一入口，具體的 log 點規格格式見<a href="/blog/testing/02-client-observability/log-point-in-spec/" data-link-title="功能規格中的 log 點定義方法" data-link-desc="把 log 點設計從 debug 階段前移到功能規格階段 — 每個功能的規格文件新增可觀測性欄位，列出啟動 / 步驟 / 錯誤 / 完成四類 log 點">功能規格中的 log 點定義方法</a>。規格中的每個 log 點屬於哪一層（連線生命週期 / protocol / 使用者行為），在<a href="/blog/testing/02-client-observability/three-layer-log-design/" data-link-title="三層 log 設計" data-link-desc="連線生命週期 log、protocol 訊息 log、使用者行為 log — 三層各自的職責、詳細程度和啟停控制">三層 log 設計</a>中定義。收集到 log 之後用自架還是商業方案處理，見<a href="/blog/testing/02-client-observability/log-endpoint-tradeoff/" data-link-title="自架 log endpoint vs 商業方案的取捨判斷" data-link-desc="自用工具用自架 log receiver（20 行 Go &#43; grep）、商業 app 用 Sentry/Crashlytics — 判斷依據是使用者規模和 debug 需求">自架 log endpoint vs 商業方案</a>的判斷流程。</p>
]]></content:encoded></item></channel></rss>