<?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>模組四：錯誤狀態與回復 on Tarragon</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/</link><description>Recent content in 模組四：錯誤狀態與回復 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/ux-design/04-error-recovery/index.xml" rel="self" type="application/rss+xml"/><item><title>錯誤訊息撰寫原則</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/</guid><description>&lt;p>錯誤訊息承擔兩個職責：讓使用者理解發生了什麼（診斷），以及讓使用者知道下一步能做什麼（行動）。缺少診斷的訊息讓使用者焦慮（「出了什麼事？」），缺少行動的訊息讓使用者卡住（「然後呢？」）。&lt;/p>
&lt;h2 id="診斷使用者能讀懂">診斷：使用者能讀懂&lt;/h2>
&lt;h3 id="用使用者的語言描述問題">用使用者的語言描述問題&lt;/h3>
&lt;p>錯誤訊息的讀者是使用者，描述問題時使用使用者能理解的詞彙。「無法連線到伺服器」比 &lt;code>ECONNREFUSED 127.0.0.1:7681&lt;/code> 更有用。技術細節對開發者有價值，但使用者需要的是「發生了什麼影響我」。&lt;/p>
&lt;p>技術細節可以保留在次要位置 — 折疊區塊、「詳細資訊」連結、或複製到剪貼簿的按鈕。進階使用者和開發者在需要時能取得，一般使用者不被打擾。&lt;/p>
&lt;h3 id="描述影響而非原因">描述影響而非原因&lt;/h3>
&lt;p>使用者關心的是「這對我意味著什麼」。「終端機暫時無法使用」比「WebSocket 連線逾時」更直接回答使用者的問題。原因是補充資訊，影響是核心資訊。&lt;/p>
&lt;h3 id="避免技術恐嚇">避免技術恐嚇&lt;/h3>
&lt;p>「嚴重錯誤」「系統崩潰」「未知的致命例外」這類措辭讓使用者以為問題很嚴重。多數情況下使用者能做的就是重試或返回 — 訊息的語氣應該反映實際的嚴重程度。&lt;/p>
&lt;h2 id="行動使用者能做什麼">行動：使用者能做什麼&lt;/h2>
&lt;h3 id="明確列出可執行的下一步">明確列出可執行的下一步&lt;/h3>
&lt;p>每個錯誤訊息至少提供一個使用者可以執行的行動。重試、返回上一頁、檢查網路連線、聯繫支援 — 行動越具體越好。&lt;/p>
&lt;p>「發生錯誤」沒有行動指引。「連線失敗，請檢查網路後重試」提供了兩個具體行動（檢查網路、重試）。&lt;/p>
&lt;h3 id="行動要可操作">行動要可操作&lt;/h3>
&lt;p>「請聯繫系統管理員」在自用工具場景中沒有意義（使用者就是管理員）。「請稍後再試」在服務完全不可用時也沒有幫助。行動建議需要考慮使用者的實際情境和能力。&lt;/p>
&lt;h3 id="提供退出路徑">提供退出路徑&lt;/h3>
&lt;p>行動選項中至少有一個是「離開當前狀態」的退出路徑 — 返回首頁、關閉對話框、取消操作。使用者可能不想重試，只想離開。畫面狀態矩陣中退出路徑為空的狀態就是 UX 死胡同（&lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機&lt;/a>）。&lt;/p>
&lt;h2 id="錯誤訊息的三層結構">錯誤訊息的三層結構&lt;/h2>
&lt;p>一個完整的錯誤訊息包含三層：&lt;/p>
&lt;p>&lt;strong>標題&lt;/strong>：一句話描述影響。「終端機連線失敗」。&lt;/p>
&lt;p>&lt;strong>說明&lt;/strong>：補充原因和 context。「伺服器沒有回應，可能是網路問題或伺服器未啟動。」&lt;/p>
&lt;p>&lt;strong>行動&lt;/strong>：按鈕或連結。「重試」「返回首頁」「查看詳細資訊」。&lt;/p>
&lt;p>三層不需要都顯示。輕微錯誤（Snackbar）可以只有標題 + 一個行動按鈕。嚴重錯誤（全螢幕）三層都需要。&lt;/p>
&lt;p>錯誤訊息寫完後，使用者看到訊息的下一步通常是重試 — &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/retry-mechanism-ux/" data-link-title="Retry 機制 UX" data-link-desc="自動 vs 手動重試、指數退避 vs 立即重試 — 重試策略的選擇取決於失敗的可恢復性和使用者的等待意願">Retry 機制 UX&lt;/a> 設計重試按鈕的行為和回饋。如果重試反覆失敗，使用者需要退路而非重試迴圈，&lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口&lt;/a>提供逃生設計。Error 狀態在畫面層級的定位和退出路徑回到 &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機&lt;/a>的矩陣框架。&lt;/p></description><content:encoded><![CDATA[<p>錯誤訊息承擔兩個職責：讓使用者理解發生了什麼（診斷），以及讓使用者知道下一步能做什麼（行動）。缺少診斷的訊息讓使用者焦慮（「出了什麼事？」），缺少行動的訊息讓使用者卡住（「然後呢？」）。</p>
<h2 id="診斷使用者能讀懂">診斷：使用者能讀懂</h2>
<h3 id="用使用者的語言描述問題">用使用者的語言描述問題</h3>
<p>錯誤訊息的讀者是使用者，描述問題時使用使用者能理解的詞彙。「無法連線到伺服器」比 <code>ECONNREFUSED 127.0.0.1:7681</code> 更有用。技術細節對開發者有價值，但使用者需要的是「發生了什麼影響我」。</p>
<p>技術細節可以保留在次要位置 — 折疊區塊、「詳細資訊」連結、或複製到剪貼簿的按鈕。進階使用者和開發者在需要時能取得，一般使用者不被打擾。</p>
<h3 id="描述影響而非原因">描述影響而非原因</h3>
<p>使用者關心的是「這對我意味著什麼」。「終端機暫時無法使用」比「WebSocket 連線逾時」更直接回答使用者的問題。原因是補充資訊，影響是核心資訊。</p>
<h3 id="避免技術恐嚇">避免技術恐嚇</h3>
<p>「嚴重錯誤」「系統崩潰」「未知的致命例外」這類措辭讓使用者以為問題很嚴重。多數情況下使用者能做的就是重試或返回 — 訊息的語氣應該反映實際的嚴重程度。</p>
<h2 id="行動使用者能做什麼">行動：使用者能做什麼</h2>
<h3 id="明確列出可執行的下一步">明確列出可執行的下一步</h3>
<p>每個錯誤訊息至少提供一個使用者可以執行的行動。重試、返回上一頁、檢查網路連線、聯繫支援 — 行動越具體越好。</p>
<p>「發生錯誤」沒有行動指引。「連線失敗，請檢查網路後重試」提供了兩個具體行動（檢查網路、重試）。</p>
<h3 id="行動要可操作">行動要可操作</h3>
<p>「請聯繫系統管理員」在自用工具場景中沒有意義（使用者就是管理員）。「請稍後再試」在服務完全不可用時也沒有幫助。行動建議需要考慮使用者的實際情境和能力。</p>
<h3 id="提供退出路徑">提供退出路徑</h3>
<p>行動選項中至少有一個是「離開當前狀態」的退出路徑 — 返回首頁、關閉對話框、取消操作。使用者可能不想重試，只想離開。畫面狀態矩陣中退出路徑為空的狀態就是 UX 死胡同（<a href="/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機</a>）。</p>
<h2 id="錯誤訊息的三層結構">錯誤訊息的三層結構</h2>
<p>一個完整的錯誤訊息包含三層：</p>
<p><strong>標題</strong>：一句話描述影響。「終端機連線失敗」。</p>
<p><strong>說明</strong>：補充原因和 context。「伺服器沒有回應，可能是網路問題或伺服器未啟動。」</p>
<p><strong>行動</strong>：按鈕或連結。「重試」「返回首頁」「查看詳細資訊」。</p>
<p>三層不需要都顯示。輕微錯誤（Snackbar）可以只有標題 + 一個行動按鈕。嚴重錯誤（全螢幕）三層都需要。</p>
<p>錯誤訊息寫完後，使用者看到訊息的下一步通常是重試 — <a href="/blog/ux-design/04-error-recovery/retry-mechanism-ux/" data-link-title="Retry 機制 UX" data-link-desc="自動 vs 手動重試、指數退避 vs 立即重試 — 重試策略的選擇取決於失敗的可恢復性和使用者的等待意願">Retry 機制 UX</a> 設計重試按鈕的行為和回饋。如果重試反覆失敗，使用者需要退路而非重試迴圈，<a href="/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口</a>提供逃生設計。Error 狀態在畫面層級的定位和退出路徑回到 <a href="/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機</a>的矩陣框架。</p>
]]></content:encoded></item><item><title>Retry 機制 UX</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/retry-mechanism-ux/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/04-error-recovery/retry-mechanism-ux/</guid><description>&lt;p>重試是錯誤恢復的第一手段。重試策略的選擇取決於兩個因素：失敗是否可能自行恢復（暫時性網路中斷 vs 伺服器不存在），以及使用者是否願意等待（前景操作 vs 背景同步）。&lt;/p>
&lt;h2 id="自動重試-vs-手動重試">自動重試 vs 手動重試&lt;/h2>
&lt;h3 id="自動重試">自動重試&lt;/h3>
&lt;p>系統在失敗後自動重新嘗試，使用者不需要手動操作。適合背景操作（資料同步、事件上報、心跳檢查）和暫時性失敗（網路閃斷、server 短暫過載）。&lt;/p>
&lt;p>自動重試的 UX 要求：使用者需要知道系統正在重試。「連線中斷，正在重新連線（第 2 次嘗試）」比靜默重試更透明。如果使用者不知道系統在重試，靜默的等待會被解讀為「系統卡住了」。&lt;/p>
&lt;p>自動重試必須有上限。無限重試在不可恢復的失敗場景中（伺服器已關閉、認證已過期）浪費資源和電量，且使用者無法察覺問題。&lt;/p>
&lt;h3 id="手動重試">手動重試&lt;/h3>
&lt;p>使用者點擊「重試」按鈕觸發重新嘗試。適合前景操作（使用者主動發起的連線、提交、搜尋）和需要使用者確認意圖的場景。&lt;/p>
&lt;p>手動重試的 UX 要求：重試按鈕在 error 畫面上明顯可見，旁邊有退出路徑（返回按鈕）。使用者可以選擇重試或放棄。&lt;/p>
&lt;h3 id="混合策略">混合策略&lt;/h3>
&lt;p>先自動重試 N 次，失敗後切換到手動重試。這是連線類操作的常見模式 — WebSocket 斷線後自動重連 3 次，3 次都失敗後顯示「連線失敗」+ 手動重連按鈕。&lt;/p>
&lt;h2 id="重試間隔策略">重試間隔策略&lt;/h2>
&lt;h3 id="立即重試">立即重試&lt;/h3>
&lt;p>失敗後立即重新嘗試，中間沒有等待。適合極短暫的瞬態失敗（DNS 解析偶發失敗、TCP 連線被 reset）。&lt;/p>
&lt;p>立即重試的風險是在 server 過載時加劇問題 — 多個 client 同時立即重試產生 thundering herd 效應。&lt;/p>
&lt;h3 id="固定間隔重試">固定間隔重試&lt;/h3>
&lt;p>每次重試間隔固定時間（例如每 5 秒重試一次）。簡單可預測，使用者能估算等待時間。&lt;/p>
&lt;h3 id="指數退避exponential-backoff">指數退避（exponential backoff）&lt;/h3>
&lt;p>每次重試的間隔加倍。第一次 1 秒、第二次 2 秒、第三次 4 秒、第四次 8 秒。加上隨機抖動（jitter）避免多個 client 同步重試。&lt;/p>
&lt;p>指數退避適合 server 端過載或暫時不可用的場景。間隔越來越長給 server 恢復的時間，同時減少 client 的資源消耗。&lt;/p>
&lt;p>指數退避的 UX 挑戰是使用者感知到的等待越來越長。第四次重試等 8 秒時使用者可能已經失去耐心。解法是顯示倒數計時（「12 秒後自動重試」）和手動重試按鈕（使用者可以跳過等待立即重試）。&lt;/p>
&lt;h2 id="重試狀態的-ui-呈現">重試狀態的 UI 呈現&lt;/h2>
&lt;p>使用者需要知道三件事：系統正在重試、已經重試了幾次、下一次重試在什麼時候。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">連線失敗，正在重新連線...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">第 2 次嘗試（共 5 次上限）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">下次重試：8 秒後 [立即重試] [返回首頁]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重試達到上限後，UI 從「重試中」切換到「失敗」狀態，顯示手動重試和退出路徑。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>部分功能不可用的降級設計 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/degraded-mode-design/" data-link-title="Degraded mode 設計" data-link-desc="部分功能不可用時怎麼告知使用者 — 靜默隱藏 vs 明確標示 vs 替代方案的設計取捨">Degraded mode 設計&lt;/a>&lt;/li>
&lt;li>重試循環的逃生口 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口&lt;/a>&lt;/li>
&lt;li>Gate 失敗的 fallback → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/" data-link-title="模組二：Gate 與 Fallback 設計" data-link-desc="Biometric / Network / Auth / Permission — 每個 gate 成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">ux-design 模組二 Gate 與 Fallback&lt;/a>&lt;/li>
&lt;li>Server 端的限速機制（影響 retry 策略設計）→ &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">DevOps 流量管控&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>重試是錯誤恢復的第一手段。重試策略的選擇取決於兩個因素：失敗是否可能自行恢復（暫時性網路中斷 vs 伺服器不存在），以及使用者是否願意等待（前景操作 vs 背景同步）。</p>
<h2 id="自動重試-vs-手動重試">自動重試 vs 手動重試</h2>
<h3 id="自動重試">自動重試</h3>
<p>系統在失敗後自動重新嘗試，使用者不需要手動操作。適合背景操作（資料同步、事件上報、心跳檢查）和暫時性失敗（網路閃斷、server 短暫過載）。</p>
<p>自動重試的 UX 要求：使用者需要知道系統正在重試。「連線中斷，正在重新連線（第 2 次嘗試）」比靜默重試更透明。如果使用者不知道系統在重試，靜默的等待會被解讀為「系統卡住了」。</p>
<p>自動重試必須有上限。無限重試在不可恢復的失敗場景中（伺服器已關閉、認證已過期）浪費資源和電量，且使用者無法察覺問題。</p>
<h3 id="手動重試">手動重試</h3>
<p>使用者點擊「重試」按鈕觸發重新嘗試。適合前景操作（使用者主動發起的連線、提交、搜尋）和需要使用者確認意圖的場景。</p>
<p>手動重試的 UX 要求：重試按鈕在 error 畫面上明顯可見，旁邊有退出路徑（返回按鈕）。使用者可以選擇重試或放棄。</p>
<h3 id="混合策略">混合策略</h3>
<p>先自動重試 N 次，失敗後切換到手動重試。這是連線類操作的常見模式 — WebSocket 斷線後自動重連 3 次，3 次都失敗後顯示「連線失敗」+ 手動重連按鈕。</p>
<h2 id="重試間隔策略">重試間隔策略</h2>
<h3 id="立即重試">立即重試</h3>
<p>失敗後立即重新嘗試，中間沒有等待。適合極短暫的瞬態失敗（DNS 解析偶發失敗、TCP 連線被 reset）。</p>
<p>立即重試的風險是在 server 過載時加劇問題 — 多個 client 同時立即重試產生 thundering herd 效應。</p>
<h3 id="固定間隔重試">固定間隔重試</h3>
<p>每次重試間隔固定時間（例如每 5 秒重試一次）。簡單可預測，使用者能估算等待時間。</p>
<h3 id="指數退避exponential-backoff">指數退避（exponential backoff）</h3>
<p>每次重試的間隔加倍。第一次 1 秒、第二次 2 秒、第三次 4 秒、第四次 8 秒。加上隨機抖動（jitter）避免多個 client 同步重試。</p>
<p>指數退避適合 server 端過載或暫時不可用的場景。間隔越來越長給 server 恢復的時間，同時減少 client 的資源消耗。</p>
<p>指數退避的 UX 挑戰是使用者感知到的等待越來越長。第四次重試等 8 秒時使用者可能已經失去耐心。解法是顯示倒數計時（「12 秒後自動重試」）和手動重試按鈕（使用者可以跳過等待立即重試）。</p>
<h2 id="重試狀態的-ui-呈現">重試狀態的 UI 呈現</h2>
<p>使用者需要知道三件事：系統正在重試、已經重試了幾次、下一次重試在什麼時候。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">連線失敗，正在重新連線...
</span></span><span class="line"><span class="ln">2</span><span class="cl">第 2 次嘗試（共 5 次上限）
</span></span><span class="line"><span class="ln">3</span><span class="cl">下次重試：8 秒後 [立即重試] [返回首頁]</span></span></code></pre></div><p>重試達到上限後，UI 從「重試中」切換到「失敗」狀態，顯示手動重試和退出路徑。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>部分功能不可用的降級設計 → <a href="/blog/ux-design/04-error-recovery/degraded-mode-design/" data-link-title="Degraded mode 設計" data-link-desc="部分功能不可用時怎麼告知使用者 — 靜默隱藏 vs 明確標示 vs 替代方案的設計取捨">Degraded mode 設計</a></li>
<li>重試循環的逃生口 → <a href="/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口</a></li>
<li>Gate 失敗的 fallback → <a href="/blog/ux-design/02-gate-fallback/" data-link-title="模組二：Gate 與 Fallback 設計" data-link-desc="Biometric / Network / Auth / Permission — 每個 gate 成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">ux-design 模組二 Gate 與 Fallback</a></li>
<li>Server 端的限速機制（影響 retry 策略設計）→ <a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">DevOps 流量管控</a></li>
</ul>
]]></content:encoded></item><item><title>Degraded mode 設計</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/degraded-mode-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/04-error-recovery/degraded-mode-design/</guid><description>&lt;p>Degraded mode 是指系統的部分功能因為外部依賴不可用（網路斷線、服務故障、權限缺失）而暫時無法運作，但其他功能仍可正常使用。設計重點是讓使用者清楚知道哪些功能可用、哪些暫時不可用，而非讓整個 app 因為一個功能的失敗而停擺。&lt;/p>
&lt;h2 id="三種處理方式">三種處理方式&lt;/h2>
&lt;h3 id="靜默隱藏">靜默隱藏&lt;/h3>
&lt;p>把不可用的功能從 UI 上移除 — 按鈕消失、選單項目隱藏。使用者看不到這些功能，自然不會嘗試使用。&lt;/p>
&lt;p>靜默隱藏的風險是使用者困惑。經常使用的功能突然消失，使用者會以為是 bug 或 app 更新移除了功能。如果功能在恢復後重新出現，使用者的困惑加劇。&lt;/p>
&lt;p>靜默隱藏只適合使用者從未使用過的功能（新使用者尚未配對時隱藏連線按鈕）。已經使用過的功能突然隱藏會破壞使用者的心理模型。&lt;/p>
&lt;h3 id="明確標示">明確標示&lt;/h3>
&lt;p>功能的 UI 元素保留在畫面上，但加上不可用的視覺標示 — 灰色按鈕、「離線不可用」標籤、禁用狀態。使用者能看到功能存在，也知道目前暫時無法使用。&lt;/p>
&lt;p>明確標示的設計要點：&lt;/p>
&lt;p>&lt;strong>說明原因&lt;/strong>。「搜尋功能需要網路連線」比單純的灰色按鈕提供更多資訊。使用者知道原因後能自行判斷要等還是離開。&lt;/p>
&lt;p>&lt;strong>說明恢復條件&lt;/strong>。「連上網路後自動恢復」讓使用者知道什麼時候功能會回來。「重新啟動 app 後可用」讓使用者知道需要採取行動。&lt;/p>
&lt;p>&lt;strong>避免只靠顏色傳達狀態&lt;/strong>。灰色按鈕對色盲使用者可能不明顯。搭配文字標籤或圖示。&lt;/p>
&lt;h3 id="替代方案">替代方案&lt;/h3>
&lt;p>提供不需要失敗依賴的替代功能。線上搜尋不可用時提供離線搜尋（本地快取的資料）。即時同步不可用時提供本地儲存（恢復連線後自動同步）。&lt;/p>
&lt;p>替代方案的 UX 需要讓使用者知道目前使用的是替代版本。「離線模式 — 搜尋結果可能不是最新的」讓使用者對結果的準確度有正確預期。&lt;/p>
&lt;h2 id="全域-vs-功能級降級">全域 vs 功能級降級&lt;/h2>
&lt;h3 id="全域降級">全域降級&lt;/h3>
&lt;p>整個 app 進入降級模式 — 頂部顯示「離線模式」橫幅，所有需要網路的功能統一標示為不可用。適合網路連線是 app 核心依賴的場景。&lt;/p>
&lt;p>全域降級的 UI 實作簡單（一個全域狀態控制所有功能的可用性），但可能過度限制 — 部分功能不依賴網路也能運作。&lt;/p>
&lt;h3 id="功能級降級">功能級降級&lt;/h3>
&lt;p>每個功能獨立判斷自己的可用狀態。搜尋需要網路但筆記不需要 — 網路斷線時搜尋不可用，筆記正常。&lt;/p>
&lt;p>功能級降級更精確但實作更複雜 — 每個功能需要宣告自己的依賴，並在依賴不可用時提供對應的 UI 狀態。&lt;/p>
&lt;h2 id="降級狀態的進入和退出">降級狀態的進入和退出&lt;/h2>
&lt;h3 id="進入">進入&lt;/h3>
&lt;p>依賴不可用時自動進入降級狀態。進入時通知使用者（Snackbar、橫幅、狀態變更）。&lt;/p>
&lt;p>避免頻繁切換 — 網路訊號不穩定時，每秒在正常和降級之間切換會讓 UI 閃爍。加入穩定性判斷（連續 N 秒不可用才進入降級，連續 N 秒可用才退出降級）。&lt;/p>
&lt;h3 id="退出">退出&lt;/h3>
&lt;p>依賴恢復後自動退出降級狀態。退出時通知使用者（「已恢復連線」），並自動執行待完成的操作（同步、補發事件）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>錯誤訊息的撰寫 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則&lt;/a>&lt;/li>
&lt;li>重試循環的逃生口 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口&lt;/a>&lt;/li>
&lt;li>網路 gate 的 UX 處理 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">ux-design 模組二 網路斷線 UX 模式&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Degraded mode 是指系統的部分功能因為外部依賴不可用（網路斷線、服務故障、權限缺失）而暫時無法運作，但其他功能仍可正常使用。設計重點是讓使用者清楚知道哪些功能可用、哪些暫時不可用，而非讓整個 app 因為一個功能的失敗而停擺。</p>
<h2 id="三種處理方式">三種處理方式</h2>
<h3 id="靜默隱藏">靜默隱藏</h3>
<p>把不可用的功能從 UI 上移除 — 按鈕消失、選單項目隱藏。使用者看不到這些功能，自然不會嘗試使用。</p>
<p>靜默隱藏的風險是使用者困惑。經常使用的功能突然消失，使用者會以為是 bug 或 app 更新移除了功能。如果功能在恢復後重新出現，使用者的困惑加劇。</p>
<p>靜默隱藏只適合使用者從未使用過的功能（新使用者尚未配對時隱藏連線按鈕）。已經使用過的功能突然隱藏會破壞使用者的心理模型。</p>
<h3 id="明確標示">明確標示</h3>
<p>功能的 UI 元素保留在畫面上，但加上不可用的視覺標示 — 灰色按鈕、「離線不可用」標籤、禁用狀態。使用者能看到功能存在，也知道目前暫時無法使用。</p>
<p>明確標示的設計要點：</p>
<p><strong>說明原因</strong>。「搜尋功能需要網路連線」比單純的灰色按鈕提供更多資訊。使用者知道原因後能自行判斷要等還是離開。</p>
<p><strong>說明恢復條件</strong>。「連上網路後自動恢復」讓使用者知道什麼時候功能會回來。「重新啟動 app 後可用」讓使用者知道需要採取行動。</p>
<p><strong>避免只靠顏色傳達狀態</strong>。灰色按鈕對色盲使用者可能不明顯。搭配文字標籤或圖示。</p>
<h3 id="替代方案">替代方案</h3>
<p>提供不需要失敗依賴的替代功能。線上搜尋不可用時提供離線搜尋（本地快取的資料）。即時同步不可用時提供本地儲存（恢復連線後自動同步）。</p>
<p>替代方案的 UX 需要讓使用者知道目前使用的是替代版本。「離線模式 — 搜尋結果可能不是最新的」讓使用者對結果的準確度有正確預期。</p>
<h2 id="全域-vs-功能級降級">全域 vs 功能級降級</h2>
<h3 id="全域降級">全域降級</h3>
<p>整個 app 進入降級模式 — 頂部顯示「離線模式」橫幅，所有需要網路的功能統一標示為不可用。適合網路連線是 app 核心依賴的場景。</p>
<p>全域降級的 UI 實作簡單（一個全域狀態控制所有功能的可用性），但可能過度限制 — 部分功能不依賴網路也能運作。</p>
<h3 id="功能級降級">功能級降級</h3>
<p>每個功能獨立判斷自己的可用狀態。搜尋需要網路但筆記不需要 — 網路斷線時搜尋不可用，筆記正常。</p>
<p>功能級降級更精確但實作更複雜 — 每個功能需要宣告自己的依賴，並在依賴不可用時提供對應的 UI 狀態。</p>
<h2 id="降級狀態的進入和退出">降級狀態的進入和退出</h2>
<h3 id="進入">進入</h3>
<p>依賴不可用時自動進入降級狀態。進入時通知使用者（Snackbar、橫幅、狀態變更）。</p>
<p>避免頻繁切換 — 網路訊號不穩定時，每秒在正常和降級之間切換會讓 UI 閃爍。加入穩定性判斷（連續 N 秒不可用才進入降級，連續 N 秒可用才退出降級）。</p>
<h3 id="退出">退出</h3>
<p>依賴恢復後自動退出降級狀態。退出時通知使用者（「已恢復連線」），並自動執行待完成的操作（同步、補發事件）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>錯誤訊息的撰寫 → <a href="/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則</a></li>
<li>重試循環的逃生口 → <a href="/blog/ux-design/04-error-recovery/error-loop-escape/" data-link-title="error → retry → error 循環的逃生口設計" data-link-desc="當重試持續失敗時，使用者需要第二條路 — 逃生口設計讓使用者能離開失敗循環而非被困住">error → retry → error 循環的逃生口</a></li>
<li>網路 gate 的 UX 處理 → <a href="/blog/ux-design/02-gate-fallback/network-offline-ux/" data-link-title="網路斷線 UX 模式" data-link-desc="Offline-first / retry / degraded mode 三種網路 gate 的處理策略 — 取決於功能是否依賴即時連線">ux-design 模組二 網路斷線 UX 模式</a></li>
</ul>
]]></content:encoded></item><item><title>error → retry → error 循環的逃生口設計</title><link>https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-loop-escape/</guid><description>&lt;p>error → retry → error 循環是指使用者遇到錯誤、點擊重試、再次失敗、再次重試的迴圈。當底層問題持續存在時（伺服器關閉、認證過期、硬體故障），重試永遠不會成功。使用者被困在這個迴圈中，唯一的出路是殺掉 app。&lt;/p>
&lt;h2 id="循環產生的條件">循環產生的條件&lt;/h2>
&lt;p>三個條件同時成立時產生困住使用者的重試循環：&lt;/p>
&lt;p>&lt;strong>錯誤持續存在&lt;/strong>。暫時性錯誤（網路閃斷）會在重試中自行恢復。持續性錯誤（伺服器已關閉）不會因重試而改變。&lt;/p>
&lt;p>&lt;strong>UI 只提供重試選項&lt;/strong>。Error 畫面上唯一的按鈕是「重試」，沒有返回、取消或其他替代路徑。&lt;/p>
&lt;p>&lt;strong>沒有重試次數上限&lt;/strong>。使用者可以無限重試，每次都失敗，每次都回到同一個 error 畫面。&lt;/p>
&lt;p>app_tunnel 修復前的 error 和 disconnected 狀態就是這個模式 — 有重連按鈕但沒有 back 按鈕。重連失敗時使用者只能再次重連，無法返回首頁（&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1&lt;/a>）。&lt;/p>
&lt;h2 id="逃生口設計">逃生口設計&lt;/h2>
&lt;h3 id="每個-error-畫面至少兩個選項">每個 error 畫面至少兩個選項&lt;/h3>
&lt;p>重試按鈕旁邊放一個退出路徑。「重試」+「返回首頁」是最小組合。使用者想繼續嘗試就重試，想離開就返回。&lt;/p>
&lt;p>這和&lt;a href="https://tarrragon.github.io/blog/ux-design/knowledge-cards/screen-state-matrix/" data-link-title="畫面狀態矩陣" data-link-desc="說明用四欄表格（顯示/可用操作/進入條件/退出路徑）系統性地暴露畫面導航缺口的設計工具">畫面狀態矩陣&lt;/a>的退出路徑要求一致 — 每個狀態至少一條退出路徑（&lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">定義與填寫方法&lt;/a>）。Error 狀態的退出路徑包括重試（回到 connecting 狀態）和返回（離開當前畫面）。&lt;/p>
&lt;h3 id="自動重試達上限後切換-ui">自動重試達上限後切換 UI&lt;/h3>
&lt;p>自動重試有固定上限（例如 3 次或 5 次）。達到上限後，UI 從「正在重試」切換到「連線失敗」，提供手動重試和退出路徑。&lt;/p>
&lt;p>切換 UI 的意義是向使用者傳達「自動恢復已經嘗試過了，需要你來判斷接下來怎麼做」。使用者可能知道問題的原因（忘了開伺服器、WiFi 沒連上），手動修正後再重試。&lt;/p>
&lt;h3 id="提供問題診斷線索">提供問題診斷線索&lt;/h3>
&lt;p>在 error 畫面提供足夠的資訊讓使用者判斷是否值得繼續重試。「伺服器沒有回應」和「認證已過期」是不同的問題 — 前者可能重試會成功（伺服器正在重啟），後者重試不會改變結果（需要重新登入）。&lt;/p>
&lt;p>診斷資訊幫助使用者做出正確決策：繼續重試、返回重新操作、或完全離開。&lt;/p>
&lt;h3 id="替代操作路徑">替代操作路徑&lt;/h3>
&lt;p>除了重試和返回，某些場景可以提供第三條路。連線特定伺服器失敗時，提供「選擇其他伺服器」的選項。認證失敗時，提供「用其他方式登入」的選項。&lt;/p>
&lt;p>替代路徑把「失敗 → 重試同樣的操作」擴展成「失敗 → 嘗試不同的操作」，增加使用者脫離困境的機會。&lt;/p>
&lt;h2 id="檢查方法">檢查方法&lt;/h2>
&lt;p>用畫面狀態矩陣檢查 error 和 retry 狀態：&lt;/p>
&lt;ol>
&lt;li>找到所有 error 狀態（矩陣中 type = error 的行）&lt;/li>
&lt;li>檢查每個 error 狀態的「可用操作」欄 — 是否除了「重試」之外還有其他操作&lt;/li>
&lt;li>檢查每個 error 狀態的「退出路徑」欄 — 是否有離開當前畫面的路徑&lt;/li>
&lt;li>操作欄只有「重試」且退出路徑為空 = 潛在的 retry loop&lt;/li>
&lt;/ol>
&lt;p>這個檢查在設計階段就能完成，成本遠低於實機測試時才發現使用者被困住。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>錯誤訊息如何引導使用者行動 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則&lt;/a>&lt;/li>
&lt;li>重試策略的選擇 → &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/retry-mechanism-ux/" data-link-title="Retry 機制 UX" data-link-desc="自動 vs 手動重試、指數退避 vs 立即重試 — 重試策略的選擇取決於失敗的可恢復性和使用者的等待意願">Retry 機制 UX&lt;/a>&lt;/li>
&lt;li>畫面狀態矩陣的退出路徑檢查 → &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機&lt;/a>&lt;/li>
&lt;li>Mock 遮蔽 error 場景的問題 → &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">testing 模組一 測試策略分層&lt;/a>&lt;/li>
&lt;li>Error 事件的分類與收集 → &lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/" data-link-title="模組一：監控心智模型" data-link-desc="四類事件（event / error / metric / lifecycle）的分類與收集策略">monitoring 模組一 監控心智模型&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>error → retry → error 循環是指使用者遇到錯誤、點擊重試、再次失敗、再次重試的迴圈。當底層問題持續存在時（伺服器關閉、認證過期、硬體故障），重試永遠不會成功。使用者被困在這個迴圈中，唯一的出路是殺掉 app。</p>
<h2 id="循環產生的條件">循環產生的條件</h2>
<p>三個條件同時成立時產生困住使用者的重試循環：</p>
<p><strong>錯誤持續存在</strong>。暫時性錯誤（網路閃斷）會在重試中自行恢復。持續性錯誤（伺服器已關閉）不會因重試而改變。</p>
<p><strong>UI 只提供重試選項</strong>。Error 畫面上唯一的按鈕是「重試」，沒有返回、取消或其他替代路徑。</p>
<p><strong>沒有重試次數上限</strong>。使用者可以無限重試，每次都失敗，每次都回到同一個 error 畫面。</p>
<p>app_tunnel 修復前的 error 和 disconnected 狀態就是這個模式 — 有重連按鈕但沒有 back 按鈕。重連失敗時使用者只能再次重連，無法返回首頁（<a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1</a>）。</p>
<h2 id="逃生口設計">逃生口設計</h2>
<h3 id="每個-error-畫面至少兩個選項">每個 error 畫面至少兩個選項</h3>
<p>重試按鈕旁邊放一個退出路徑。「重試」+「返回首頁」是最小組合。使用者想繼續嘗試就重試，想離開就返回。</p>
<p>這和<a href="/blog/ux-design/knowledge-cards/screen-state-matrix/" data-link-title="畫面狀態矩陣" data-link-desc="說明用四欄表格（顯示/可用操作/進入條件/退出路徑）系統性地暴露畫面導航缺口的設計工具">畫面狀態矩陣</a>的退出路徑要求一致 — 每個狀態至少一條退出路徑（<a href="/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">定義與填寫方法</a>）。Error 狀態的退出路徑包括重試（回到 connecting 狀態）和返回（離開當前畫面）。</p>
<h3 id="自動重試達上限後切換-ui">自動重試達上限後切換 UI</h3>
<p>自動重試有固定上限（例如 3 次或 5 次）。達到上限後，UI 從「正在重試」切換到「連線失敗」，提供手動重試和退出路徑。</p>
<p>切換 UI 的意義是向使用者傳達「自動恢復已經嘗試過了，需要你來判斷接下來怎麼做」。使用者可能知道問題的原因（忘了開伺服器、WiFi 沒連上），手動修正後再重試。</p>
<h3 id="提供問題診斷線索">提供問題診斷線索</h3>
<p>在 error 畫面提供足夠的資訊讓使用者判斷是否值得繼續重試。「伺服器沒有回應」和「認證已過期」是不同的問題 — 前者可能重試會成功（伺服器正在重啟），後者重試不會改變結果（需要重新登入）。</p>
<p>診斷資訊幫助使用者做出正確決策：繼續重試、返回重新操作、或完全離開。</p>
<h3 id="替代操作路徑">替代操作路徑</h3>
<p>除了重試和返回，某些場景可以提供第三條路。連線特定伺服器失敗時，提供「選擇其他伺服器」的選項。認證失敗時，提供「用其他方式登入」的選項。</p>
<p>替代路徑把「失敗 → 重試同樣的操作」擴展成「失敗 → 嘗試不同的操作」，增加使用者脫離困境的機會。</p>
<h2 id="檢查方法">檢查方法</h2>
<p>用畫面狀態矩陣檢查 error 和 retry 狀態：</p>
<ol>
<li>找到所有 error 狀態（矩陣中 type = error 的行）</li>
<li>檢查每個 error 狀態的「可用操作」欄 — 是否除了「重試」之外還有其他操作</li>
<li>檢查每個 error 狀態的「退出路徑」欄 — 是否有離開當前畫面的路徑</li>
<li>操作欄只有「重試」且退出路徑為空 = 潛在的 retry loop</li>
</ol>
<p>這個檢查在設計階段就能完成，成本遠低於實機測試時才發現使用者被困住。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>錯誤訊息如何引導使用者行動 → <a href="/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則</a></li>
<li>重試策略的選擇 → <a href="/blog/ux-design/04-error-recovery/retry-mechanism-ux/" data-link-title="Retry 機制 UX" data-link-desc="自動 vs 手動重試、指數退避 vs 立即重試 — 重試策略的選擇取決於失敗的可恢復性和使用者的等待意願">Retry 機制 UX</a></li>
<li>畫面狀態矩陣的退出路徑檢查 → <a href="/blog/ux-design/01-screen-state-machine/" data-link-title="模組一：畫面狀態機設計" data-link-desc="畫面狀態矩陣（顯示 / 操作 / 進入 / 退出）— 退出路徑為空 = UX 死胡同">ux-design 模組一 畫面狀態機</a></li>
<li>Mock 遮蔽 error 場景的問題 → <a href="/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">testing 模組一 測試策略分層</a></li>
<li>Error 事件的分類與收集 → <a href="/blog/monitoring/01-mental-model/" data-link-title="模組一：監控心智模型" data-link-desc="四類事件（event / error / metric / lifecycle）的分類與收集策略">monitoring 模組一 監控心智模型</a></li>
</ul>
]]></content:encoded></item></channel></rss>