<?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>Input-Validation on Tarragon</title><link>https://tarrragon.github.io/blog/tags/input-validation/</link><description>Recent content in Input-Validation on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 16 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/input-validation/index.xml" rel="self" type="application/rss+xml"/><item><title>U.C7 商品條碼的誤導性查無結果 — 可本地判定的錯誤讓使用者白等 API</title><link>https://tarrragon.github.io/blog/ux-design/cases/misleading-no-result-for-product-barcode/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/misleading-no-result-for-product-barcode/</guid><description>&lt;p>book_overview_app 是一個 Flutter 書庫管理教學專案，以下案例取自該專案的實機測試。錯誤回饋的責任是回答使用者下一步該做什麼——一個字面誠實的訊息，若把可本地判定的輸入錯誤混進「查無結果」，就是誤導。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>book_overview_app 的掃描器偵測所有 EAN-13 格式條碼。書籍 ISBN-13 一定以 978 或 979 開頭；其他開頭的 EAN-13 是一般商品條碼（飲料、餅乾、日用品）。使用者對準商品條碼掃描時：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>修復前&lt;/th>
 &lt;th>修復後（W1-089）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>判定位置&lt;/td>
 &lt;td>送 Google Books API 遠端查詢&lt;/td>
 &lt;td>&lt;code>IsbnValidationService.isBookIsbn()&lt;/code> 本地判定 prefix&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>等待時間&lt;/td>
 &lt;td>2 秒以上&lt;/td>
 &lt;td>即時&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回饋訊息&lt;/td>
 &lt;td>「查無結果」&lt;/td>
 &lt;td>「此條碼非書籍 ISBN，請掃描書背 ISBN 條碼」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者理解&lt;/td>
 &lt;td>「這本書資料庫沒有」（錯誤歸因）&lt;/td>
 &lt;td>「我掃錯條碼了」（正確歸因）＋知道下一步&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>掃描狀態&lt;/td>
 &lt;td>中斷&lt;/td>
 &lt;td>維持掃描中，對準正確條碼即續掃&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>修復是一個本地 prefix 檢查：非 978/979 開頭的 13 位條碼不進入 ISBN 驗證與 API 查詢流程，直接顯示本地化提示並停留在掃描狀態。978/979 開頭維持既有流程。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>可本地判定的錯誤該在本地即時回應&lt;/strong>。「這個條碼是不是書籍 ISBN」是純 prefix 規則，毫秒級本地可判。把它丟給 API 等於用 2 秒網路延遲換一個本來就知道的答案 — 違反回饋時間門檻之外，更浪費了「立即告訴使用者掃錯了」的機會。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>「查無結果」誠實但誤導&lt;/strong>。API 確實查無結果，訊息字面為真。但使用者的歸因是「這本書資料庫沒收錄」，於是重掃、換角度、懷疑 app 壞掉 — 沒有一條路通往真正的解法「去掃書背的 ISBN 條碼」。回饋若不能修正使用者的心智模型，字面誠實沒有價值。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>兩種「找不到」是不同的使用者情境，混用同一訊息給錯心智模型&lt;/strong>。「輸入本身不合法」（掃錯條碼，本地可判）和「輸入合法但查詢無結果」（確實查無此書，需遠端）需要不同的下一步：前者是換條碼，後者是手動輸入或放棄。訊息合併等於系統放棄了它明明有能力做的錯誤分類。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>輸入驗證前移&lt;/strong>：格式與領域規則（長度、prefix、checksum）在送出前本地攔截，遠端只處理「合法輸入的查詢結果」。設計時對每個送遠端的輸入列一張「本地可判定規則」清單。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>錯誤訊息以下一步行動為中心撰寫&lt;/strong>：訊息模板是「發生了什麼 + 該做什麼」（「此條碼非書籍 ISBN，請掃描書背 ISBN 條碼」），不是系統視角的查詢結果陳述。寫完自問：使用者讀完知道下一步嗎？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>稽核每個「查無結果」訊息&lt;/strong>：對既有系統，逐一檢查回「查無結果／找不到」的路徑，問「有沒有本地可判定的原因被混進來」。有，就拆成獨立的即時提示。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>輸入機制的四維度決策 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制設計&lt;/a>&lt;/li>
&lt;li>結果通知屬於三層回饋的第三層 → &lt;a href="https://tarrragon.github.io/blog/ux-design/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型&lt;/a>&lt;/li>
&lt;li>類似案例（回饋缺失）→ &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/export-button-zero-feedback/" data-link-title="U.C5 匯出按鈕按下零回饋 — 狀態機完備但 UI 沒接線" data-link-desc="Flutter app 匯出設定頁的確認匯出按鈕 onPressed 是空 callback，按下畫面毫無變化 — 使用者無法分辨匯出成功、進行中、還是功能根本沒做。ViewModel 的 idle/inProgress/completed/failed 狀態機早已完備，缺的只是頁面接線與三層回饋">U.C5 匯出按鈕零回饋&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>book_overview_app 是一個 Flutter 書庫管理教學專案，以下案例取自該專案的實機測試。錯誤回饋的責任是回答使用者下一步該做什麼——一個字面誠實的訊息，若把可本地判定的輸入錯誤混進「查無結果」，就是誤導。</p>
<h2 id="觀察">觀察</h2>
<p>book_overview_app 的掃描器偵測所有 EAN-13 格式條碼。書籍 ISBN-13 一定以 978 或 979 開頭；其他開頭的 EAN-13 是一般商品條碼（飲料、餅乾、日用品）。使用者對準商品條碼掃描時：</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>修復前</th>
          <th>修復後（W1-089）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>判定位置</td>
          <td>送 Google Books API 遠端查詢</td>
          <td><code>IsbnValidationService.isBookIsbn()</code> 本地判定 prefix</td>
      </tr>
      <tr>
          <td>等待時間</td>
          <td>2 秒以上</td>
          <td>即時</td>
      </tr>
      <tr>
          <td>回饋訊息</td>
          <td>「查無結果」</td>
          <td>「此條碼非書籍 ISBN，請掃描書背 ISBN 條碼」</td>
      </tr>
      <tr>
          <td>使用者理解</td>
          <td>「這本書資料庫沒有」（錯誤歸因）</td>
          <td>「我掃錯條碼了」（正確歸因）＋知道下一步</td>
      </tr>
      <tr>
          <td>掃描狀態</td>
          <td>中斷</td>
          <td>維持掃描中，對準正確條碼即續掃</td>
      </tr>
  </tbody>
</table>
<p>修復是一個本地 prefix 檢查：非 978/979 開頭的 13 位條碼不進入 ISBN 驗證與 API 查詢流程，直接顯示本地化提示並停留在掃描狀態。978/979 開頭維持既有流程。</p>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>可本地判定的錯誤該在本地即時回應</strong>。「這個條碼是不是書籍 ISBN」是純 prefix 規則，毫秒級本地可判。把它丟給 API 等於用 2 秒網路延遲換一個本來就知道的答案 — 違反回饋時間門檻之外，更浪費了「立即告訴使用者掃錯了」的機會。</p>
</li>
<li>
<p><strong>「查無結果」誠實但誤導</strong>。API 確實查無結果，訊息字面為真。但使用者的歸因是「這本書資料庫沒收錄」，於是重掃、換角度、懷疑 app 壞掉 — 沒有一條路通往真正的解法「去掃書背的 ISBN 條碼」。回饋若不能修正使用者的心智模型，字面誠實沒有價值。</p>
</li>
<li>
<p><strong>兩種「找不到」是不同的使用者情境，混用同一訊息給錯心智模型</strong>。「輸入本身不合法」（掃錯條碼，本地可判）和「輸入合法但查詢無結果」（確實查無此書，需遠端）需要不同的下一步：前者是換條碼，後者是手動輸入或放棄。訊息合併等於系統放棄了它明明有能力做的錯誤分類。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li>
<p><strong>輸入驗證前移</strong>：格式與領域規則（長度、prefix、checksum）在送出前本地攔截，遠端只處理「合法輸入的查詢結果」。設計時對每個送遠端的輸入列一張「本地可判定規則」清單。</p>
</li>
<li>
<p><strong>錯誤訊息以下一步行動為中心撰寫</strong>：訊息模板是「發生了什麼 + 該做什麼」（「此條碼非書籍 ISBN，請掃描書背 ISBN 條碼」），不是系統視角的查詢結果陳述。寫完自問：使用者讀完知道下一步嗎？</p>
</li>
<li>
<p><strong>稽核每個「查無結果」訊息</strong>：對既有系統，逐一檢查回「查無結果／找不到」的路徑，問「有沒有本地可判定的原因被混進來」。有，就拆成獨立的即時提示。</p>
</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>輸入機制的四維度決策 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制設計</a></li>
<li>結果通知屬於三層回饋的第三層 → <a href="/blog/ux-design/06-interaction-feedback/feedback-three-layers/" data-link-title="互動回饋三層模型：點擊確認、等待指示、結果通知" data-link-desc="使用者操作後的回饋依時間分層，缺層的症狀是重複提交與重複導航 — 診斷「按了沒反應」與多步驟流程卡狀態問題的檢查框架，涵蓋按鈕級與畫面級兩個尺度。">互動回饋三層模型</a></li>
<li>類似案例（回饋缺失）→ <a href="/blog/ux-design/cases/export-button-zero-feedback/" data-link-title="U.C5 匯出按鈕按下零回饋 — 狀態機完備但 UI 沒接線" data-link-desc="Flutter app 匯出設定頁的確認匯出按鈕 onPressed 是空 callback，按下畫面毫無變化 — 使用者無法分辨匯出成功、進行中、還是功能根本沒做。ViewModel 的 idle/inProgress/completed/failed 狀態機早已完備，缺的只是頁面接線與三層回饋">U.C5 匯出按鈕零回饋</a></li>
</ul>
]]></content:encoded></item></channel></rss>