<?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>Bluetooth on Tarragon</title><link>https://tarrragon.github.io/blog/tags/bluetooth/</link><description>Recent content in Bluetooth on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 08 Sep 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/bluetooth/index.xml" rel="self" type="application/rss+xml"/><item><title>兩種錯法的代價不對稱時，過濾策略要讓失敗倒向可補救的那一邊</title><link>https://tarrragon.github.io/blog/work-log/filter-strategy-follows-asymmetric-failure-cost/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/work-log/filter-strategy-follows-asymmetric-failure-cost/</guid><description>&lt;p>過濾一組候選裝置有兩個方向：列出確定要的（白名單），或列出確定不要的（黑名單）。兩個方向的正確率可以完全相同，而&lt;strong>它們錯的時候代價不同&lt;/strong>，這個不對稱決定該選哪一個。&lt;/p>
&lt;h2 id="兩種誤判落在不同的人身上">兩種誤判落在不同的人身上&lt;/h2>
&lt;p>以印表機候選清單為例：&lt;/p>
&lt;p>&lt;strong>白名單誤擋&lt;/strong>——某台印表機的宣告不符合條件，它從清單裡消失。使用者看到的是一份沒有自己印表機的清單，沒有任何操作可以把它找回來，店家當下不能出單。這個失敗&lt;strong>使用者無法補救&lt;/strong>，而且它會打擊到一整批同型號的機器。&lt;/p>
&lt;p>&lt;strong>黑名單誤放&lt;/strong>——某個不該出現的裝置留在清單裡。使用者要主動選中它才會出事。這個失敗&lt;strong>需要人為誤選才會實現&lt;/strong>，且範圍限於選錯的那一台機器。&lt;/p>
&lt;p>兩種誤判的代價不對稱時，讓失敗落在使用者有辦法補救的那一種誤判上。所以這裡選黑名單：列出確定不是印表機的用途（HID、集線器、儲存、音訊、視訊），其餘一律放行。&lt;/p>
&lt;p>這個選擇的附帶效果正好解掉&lt;a href="../usb-class-code-lives-in-two-layers/">類別代碼的兩層問題&lt;/a>留下的難題——宣告成 &lt;code>255&lt;/code> 供應商自訂的熱感印表機不在排除清單裡，所以它留得住；而觸控面板是 HID，被精準擋掉。白名單擋掉 &lt;code>255&lt;/code> 的同時擋掉的是使用者真正要用的機器。&lt;/p>
&lt;h2 id="判斷不出來的時候放行">判斷不出來的時候放行&lt;/h2>
&lt;p>排除清單需要一條配套規則：&lt;strong>取不到判斷依據時放行，不當成不合格。&lt;/strong>&lt;/p>
&lt;p>藍牙印表機沒有 USB 類別欄位，這個欄位對它而言不存在。把「讀不到值」與「值不符合」歸成同一類處置，會讓整個藍牙路徑的裝置消失——而它們是完全合法的目標。&lt;/p>
&lt;p>這條規則的一般形態是：&lt;strong>過濾條件的射程要寫清楚，射程外的對象不套用該條件。&lt;/strong> 這裡的射程是「USB 裝置」，所以條件寫成「USB 裝置且用途落在排除清單裡才排除」，而不是「用途不在允許清單裡就排除」。兩種寫法對 USB 裝置的行為相同，對藍牙裝置的行為相反。&lt;/p>
&lt;p>同一條規則也管裝置型號的推斷：程式從機器簽章字串判斷廠牌、遇到認不得的機型時，正確的輸出是「不確定」而不是猜一個。不確定的處置是引導使用者去設定外接裝置，而不是硬套某個廠牌的專屬流程——硬套的失敗形態是靜默的（呼叫了一個不存在的服務、或送出對方看不懂的指令），而引導的失敗形態是使用者知道自己要做什麼。&lt;/p>
&lt;h2 id="藍牙與-usb-是兩套彼此獨立的分類體系">藍牙與 USB 是兩套彼此獨立的分類體系&lt;/h2>
&lt;p>USB 類別代碼只存在於 USB 裝置。傳統藍牙有自己的一套——Bluetooth Class of Device，用主類別加服務位元描述，印表機落在 Imaging 大類底下的一個位元。&lt;/p>
&lt;p>兩套體系的數值沒有對應關係，把 USB 的類別代碼拿去比對藍牙裝置是型別上成立而語意上無意義的比對。而藍牙那一套本身的可靠度也有限：國產藍牙熱感機把 Class of Device 填得不正確的情形存在（同樣是現場回報、非本文核對過的清單），據以過濾就會誤擋。&lt;/p>
&lt;p>這裡的處置是&lt;strong>把過濾條件限定在 USB 裝置，藍牙一律放行&lt;/strong>。這個決定有一個具體的依據：機器內部的硬體（觸控面板、內部集線器上的裝置）不會出現在藍牙已配對清單裡，所以藍牙路徑上根本沒有「選到自己的螢幕」這種風險，過濾在那裡沒有要防的對象。&lt;strong>這兩個理由各自獨立、任一個成立就足以支持同一個處置&lt;/strong>：宣告不可靠說的是在藍牙上過濾會做錯，而內部硬體不走藍牙說的是在藍牙上根本不必過濾。&lt;/p>
&lt;h2 id="型別對而語意錯的比對不會報錯">型別對而語意錯的比對不會報錯&lt;/h2>
&lt;p>過濾條件寫對了，清單仍然可能是空的——&lt;strong>同一個症狀有第二個成因，而它落在跨層傳遞的數值表示法上&lt;/strong>。排查「清單為什麼空」時這兩個成因都要考慮，所以它跟前面幾節屬於同一個問題的不同分支，而不是另一個主題。&lt;/p>
&lt;p>原生端把 USB 的 vendor id 以十進位字串交給上層時，寫成 &lt;code>0xABCD&lt;/code> 的那個值到上層手上是 &lt;code>&amp;quot;43981&amp;quot;&lt;/code>。用十六進位字面值寫比對規則的程式不會編譯失敗、不會拋出例外，比對單純不成立——與過濾條件寫錯的表現完全一樣。&lt;/p>
&lt;p>這一類的鑑別方式是把值印出來看，而不是重讀比對邏輯。比對邏輯讀起來是對的（那個十六進位值確實是該裝置的 vendor id），錯的是兩端對「這個欄位長什麼樣」的假設不一致，而那個假設沒有寫在任何一邊的程式碼裡。&lt;strong>上面的數值是佔位的&lt;/strong>——每一層實際用什麼表示法由那一層自己決定，要知道自己這一套是哪一種，只有把值印出來看。跨層邊界上的每個識別碼欄位都值得問一次：它在這一端是什麼型別、什麼進位、什麼大小寫。&lt;/p>
&lt;h2 id="判斷標準">判斷標準&lt;/h2>
&lt;p>選白名單還是黑名單，先問兩種誤判分別落在誰身上、以及他有沒有辦法自己補救。選的方向要讓誤判落在有辦法自己補救的人身上。&lt;/p>
&lt;p>過濾條件要寫出自己的射程，射程外的對象不套用；取不到判斷依據時放行，並把「不確定」當成一個要輸出的答案，不是一個要猜掉的空缺。&lt;/p>
&lt;p>跨層邊界上的識別碼比對失效時，先印值再讀邏輯——型別對而語意錯的比對在程式碼上看起來正確。&lt;/p></description><content:encoded><![CDATA[<p>過濾一組候選裝置有兩個方向：列出確定要的（白名單），或列出確定不要的（黑名單）。兩個方向的正確率可以完全相同，而<strong>它們錯的時候代價不同</strong>，這個不對稱決定該選哪一個。</p>
<h2 id="兩種誤判落在不同的人身上">兩種誤判落在不同的人身上</h2>
<p>以印表機候選清單為例：</p>
<p><strong>白名單誤擋</strong>——某台印表機的宣告不符合條件，它從清單裡消失。使用者看到的是一份沒有自己印表機的清單，沒有任何操作可以把它找回來，店家當下不能出單。這個失敗<strong>使用者無法補救</strong>，而且它會打擊到一整批同型號的機器。</p>
<p><strong>黑名單誤放</strong>——某個不該出現的裝置留在清單裡。使用者要主動選中它才會出事。這個失敗<strong>需要人為誤選才會實現</strong>，且範圍限於選錯的那一台機器。</p>
<p>兩種誤判的代價不對稱時，讓失敗落在使用者有辦法補救的那一種誤判上。所以這裡選黑名單：列出確定不是印表機的用途（HID、集線器、儲存、音訊、視訊），其餘一律放行。</p>
<p>這個選擇的附帶效果正好解掉<a href="../usb-class-code-lives-in-two-layers/">類別代碼的兩層問題</a>留下的難題——宣告成 <code>255</code> 供應商自訂的熱感印表機不在排除清單裡，所以它留得住；而觸控面板是 HID，被精準擋掉。白名單擋掉 <code>255</code> 的同時擋掉的是使用者真正要用的機器。</p>
<h2 id="判斷不出來的時候放行">判斷不出來的時候放行</h2>
<p>排除清單需要一條配套規則：<strong>取不到判斷依據時放行，不當成不合格。</strong></p>
<p>藍牙印表機沒有 USB 類別欄位，這個欄位對它而言不存在。把「讀不到值」與「值不符合」歸成同一類處置，會讓整個藍牙路徑的裝置消失——而它們是完全合法的目標。</p>
<p>這條規則的一般形態是：<strong>過濾條件的射程要寫清楚，射程外的對象不套用該條件。</strong> 這裡的射程是「USB 裝置」，所以條件寫成「USB 裝置且用途落在排除清單裡才排除」，而不是「用途不在允許清單裡就排除」。兩種寫法對 USB 裝置的行為相同，對藍牙裝置的行為相反。</p>
<p>同一條規則也管裝置型號的推斷：程式從機器簽章字串判斷廠牌、遇到認不得的機型時，正確的輸出是「不確定」而不是猜一個。不確定的處置是引導使用者去設定外接裝置，而不是硬套某個廠牌的專屬流程——硬套的失敗形態是靜默的（呼叫了一個不存在的服務、或送出對方看不懂的指令），而引導的失敗形態是使用者知道自己要做什麼。</p>
<h2 id="藍牙與-usb-是兩套彼此獨立的分類體系">藍牙與 USB 是兩套彼此獨立的分類體系</h2>
<p>USB 類別代碼只存在於 USB 裝置。傳統藍牙有自己的一套——Bluetooth Class of Device，用主類別加服務位元描述，印表機落在 Imaging 大類底下的一個位元。</p>
<p>兩套體系的數值沒有對應關係，把 USB 的類別代碼拿去比對藍牙裝置是型別上成立而語意上無意義的比對。而藍牙那一套本身的可靠度也有限：國產藍牙熱感機把 Class of Device 填得不正確的情形存在（同樣是現場回報、非本文核對過的清單），據以過濾就會誤擋。</p>
<p>這裡的處置是<strong>把過濾條件限定在 USB 裝置，藍牙一律放行</strong>。這個決定有一個具體的依據：機器內部的硬體（觸控面板、內部集線器上的裝置）不會出現在藍牙已配對清單裡，所以藍牙路徑上根本沒有「選到自己的螢幕」這種風險，過濾在那裡沒有要防的對象。<strong>這兩個理由各自獨立、任一個成立就足以支持同一個處置</strong>：宣告不可靠說的是在藍牙上過濾會做錯，而內部硬體不走藍牙說的是在藍牙上根本不必過濾。</p>
<h2 id="型別對而語意錯的比對不會報錯">型別對而語意錯的比對不會報錯</h2>
<p>過濾條件寫對了，清單仍然可能是空的——<strong>同一個症狀有第二個成因，而它落在跨層傳遞的數值表示法上</strong>。排查「清單為什麼空」時這兩個成因都要考慮，所以它跟前面幾節屬於同一個問題的不同分支，而不是另一個主題。</p>
<p>原生端把 USB 的 vendor id 以十進位字串交給上層時，寫成 <code>0xABCD</code> 的那個值到上層手上是 <code>&quot;43981&quot;</code>。用十六進位字面值寫比對規則的程式不會編譯失敗、不會拋出例外，比對單純不成立——與過濾條件寫錯的表現完全一樣。</p>
<p>這一類的鑑別方式是把值印出來看，而不是重讀比對邏輯。比對邏輯讀起來是對的（那個十六進位值確實是該裝置的 vendor id），錯的是兩端對「這個欄位長什麼樣」的假設不一致，而那個假設沒有寫在任何一邊的程式碼裡。<strong>上面的數值是佔位的</strong>——每一層實際用什麼表示法由那一層自己決定，要知道自己這一套是哪一種，只有把值印出來看。跨層邊界上的每個識別碼欄位都值得問一次：它在這一端是什麼型別、什麼進位、什麼大小寫。</p>
<h2 id="判斷標準">判斷標準</h2>
<p>選白名單還是黑名單，先問兩種誤判分別落在誰身上、以及他有沒有辦法自己補救。選的方向要讓誤判落在有辦法自己補救的人身上。</p>
<p>過濾條件要寫出自己的射程，射程外的對象不套用；取不到判斷依據時放行，並把「不確定」當成一個要輸出的答案，不是一個要猜掉的空缺。</p>
<p>跨層邊界上的識別碼比對失效時，先印值再讀邏輯——型別對而語意錯的比對在程式碼上看起來正確。</p>
]]></content:encoded></item></channel></rss>