<?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/tags/%E8%B3%87%E6%96%99/</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>Mon, 03 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E8%B3%87%E6%96%99/index.xml" rel="self" type="application/rss+xml"/><item><title>資料多出一種形狀時，既有分析邏輯靜默換語意</title><link>https://tarrragon.github.io/blog/report/new-data-shape-silently-changes-analysis/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/report/new-data-shape-silently-changes-analysis/</guid><description>&lt;h2 id="論述基礎與限制">論述基礎與限制&lt;/h2>
&lt;p>本卡從一套自建流量統計的資料判讀抽出，同一個錯誤在兩天內發生兩次、形狀完全相同。&lt;/p>
&lt;p>第一次：原本的資料模型是每次瀏覽產生一列。判斷「這個 session 只看了一頁」的分類公式寫成 &lt;code>COUNTIFS(session欄, 當列session) = 1&lt;/code>。後來為了量停留時間，加入離開事件，每次瀏覽變成一進一離兩列。公式沒有報錯，但 &lt;code>= 1&lt;/code> 這個條件從此永遠不成立——每次瀏覽至少兩列。「單頁即走」這個分類從那一刻起恆為零，而零看起來就像「沒有這種流量」。&lt;/p>
&lt;p>第二次：修正版公式開頭加了「非進入事件的列一律留白」，避免同一次瀏覽被計數兩次。這個修正在「每次瀏覽都有進入事件」的前提下正確。真實資料裡出現了第三種形狀：某些訪客只有離開事件、沒有進入事件（接收端的執行紀錄證實那個時間點沒收到任何請求，成因未確認）。這類列被開頭那個條件整批留白，一整類流量在統計裡完全不存在。&lt;/p>
&lt;p>兩次都不是語法錯誤，也不是資料錯誤。公式合法、輸入正確、輸出是合法的分類標籤，只有分佈變了。&lt;/p>
&lt;p>證據強度要說清楚，因為它與本卡的主張層級不對稱。觀測是兩次、來自同一個系統、同一位作者、同一週之內——這個規模不足以支持任何比例宣稱，也不足以支持「這類錯誤有多常見」。&lt;strong>而核心原則不靠這兩次觀測成立&lt;/strong>：條件式編碼了未宣告的資料形狀假設，這是從條件式的性質推導出來的，兩次事件是它的例示而非樣本。分辨這兩層很重要，否則讀者會誤以為原則的可信度等於 n=2。&lt;/p>
&lt;p>限制：本卡談的是資料形狀增加——新增事件型別、新增來源、新增狀態。欄位改名或型別變更在&lt;strong>有型別檢查的環境&lt;/strong>會產生錯誤而自己現形，因此不在此列；在試算表這類沒有型別的環境它們同樣是靜默的（改欄位標題不會讓依範圍參照的公式報錯，數字被存成文字會讓求和悄悄少算），那些情形適用本卡的同一套修法。&lt;/p>
&lt;hr>
&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>分析邏輯裡的每一個條件式都編碼了一個對資料形狀的假設，而那個假設沒有住址。&lt;/strong> 資料多出一種形狀時，條件式仍然合法執行、仍然回傳值，只是它回答的問題已經和寫下它時不是同一個。&lt;/p>
&lt;p>不可見有三個成分。&lt;/p>
&lt;p>&lt;strong>假設不寫在任何地方。&lt;/strong> &lt;code>COUNTIFS(...) = 1&lt;/code> 這個式子裡的「1」承載的是「一次瀏覽產生一列」這個資料模型事實。這個事實不在公式旁邊、不在欄位定義裡、不在文件裡——它只存在於寫下公式那一刻的心智模型中，而心智模型不會隨資料模型一起更新。&lt;/p>
&lt;p>&lt;strong>錯誤的輸出與正確的輸出外觀相同。&lt;/strong> 分類邏輯回傳的仍然是合法標籤，沒有錯誤值、沒有型別衝突、沒有任何執行期的異常。要察覺它算錯，得先有一個獨立來源知道「這裡本來應該有多少」——而分析邏輯存在的理由，正是因為沒有那個獨立來源。&lt;/p>
&lt;p>&lt;strong>變更的動機讓副作用更難聯想。&lt;/strong> 新增資料形狀通常是為了收集更多資訊，動機是「知道更多」；副作用卻是某些既有統計悄悄歸零或翻倍。歸零最危險——它與「這一類根本不存在」在數字上完全不可區分，這是 &lt;a href="../lint-scope-must-be-explicit-fact/">#221&lt;/a> 描述的同一種訊號同構，只是發生在分析層而非檢查層。&lt;/p>
&lt;hr>
&lt;h2 id="修法">修法&lt;/h2>
&lt;p>&lt;strong>資料模型新增一種形狀時，把下游分析邏輯的重算當成同一次變更的一部分。&lt;/strong> 分析邏輯是資料模型的下游依賴，和欄位定義、寫入程式碼屬於同一次變更的範圍。延後處理會失去對照：之後回頭看時，看到的數字已經是新公式算出來的，沒有東西可以比對。&lt;/p>
&lt;p>&lt;strong>讓未涵蓋的資料可見，而非留白。&lt;/strong> 分類邏輯加一個 catch-all 分支，把沒被任何條件接住的列標成「未分類」。留白與「不適用」在視覺上不可區分，「未分類」則是一個會被看見的標籤——它不需要有人事先知道漏了什麼，只需要有人看一眼分佈。&lt;/p>
&lt;p>&lt;strong>用總量守恆檢查涵蓋。&lt;/strong> 分類後各類數量的總和與原始列數的差額應該為零，差額不為零代表有形狀沒被涵蓋。這個檢查的成本是一條公式，抓的卻是所有「沒想到還有這種資料」的情形——它不要求預先列舉漏掉了什麼，這正是它比逐條檢視有效的地方。&lt;/p>
&lt;p>&lt;strong>條件式裡的常數要寫下它的來源。&lt;/strong> 在 &lt;code>= 1&lt;/code> 旁註明「假設每次瀏覽一列」。這讓資料模型變更時的反向搜尋成為可能：改事件模型的人可以搜尋「每次瀏覽」，找到所有依賴這個事實的地方。沒有這行註記時，那些依賴分散在各個條件式裡，只能靠記憶列舉。&lt;/p>
&lt;hr>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;p>某個分類的計數恆為零，且沒有人記得它上次非零是什麼時候——先檢查它的條件式是否還與當前資料形狀相符，再檢查現實中是否真的沒有那一類資料。&lt;/p>
&lt;p>資料模型變更後，總量沒變但各分類的比例大幅移動——移動可能是真實的，也可能是某條件式換了語意，兩者的區別要靠對照舊公式在舊資料上的結果。&lt;/p>
&lt;p>新增了一種事件、一種來源或一種狀態，而變更範圍裡沒有任何一處提到報表或公式——這代表下游依賴尚未被盤點，不代表下游沒有依賴。&lt;/p>
&lt;hr>
&lt;h2 id="跟其他原則的關係">跟其他原則的關係&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../lint-scope-must-be-explicit-fact/">#221 檢查規則的作用域要顯式列舉&lt;/a>：兩者共享「零與未涵蓋不可區分」這個核心結構，作用層不同。#221 說的是檢查工具的納管範圍（未納管目錄的零 error 與合規目錄的零 error 訊號相同），本卡說的是分析邏輯的涵蓋範圍（未被條件接住的資料與不存在的資料計數相同）。處置方向也同構——都是把隱含的範圍變成顯式的列舉。&lt;/li>
&lt;li>&lt;a href="../self-audit-detection-method-must-match-rule-type/">#232 自審 sweep 的偵測方法要對齊規則類型&lt;/a>：#232 講偵測方法要看得見違反形態才可信，本卡是它在資料層的對應——分析邏輯要涵蓋得到某種資料形狀，它的計數才可信。兩者都在回答「這個 clean / 這個零，是通過還是沒被看到」。&lt;/li>
&lt;li>&lt;a href="../proportional-scaling-is-not-space-allocation/">#228 等比縮放不管空間分配&lt;/a>：#228 指出工具的保證只涵蓋它工作的那一層，問題發生在別層時工具的靜默是「沒看到」而非「沒有」。本卡把同一件事推到分析邏輯：公式的保證只涵蓋它被寫下時的資料形狀，新形狀出現時它的合法輸出是「沒看到」而非「沒有」。&lt;/li>
&lt;li>&lt;a href="../declared-composition-is-not-performed-composition/">#239 宣告的組合不等於執行的組合&lt;/a>：兩者都描述「存在」與「作用」之間的落差。#239 的落差在規則層（規則寫下了但沒被執行），本卡的落差在邏輯層（條件式執行了但已經不對應原本的問題）。本卡多一個條件——落差是由另一次正確的變更引入的，因此在變更當下沒有任何人做錯事。&lt;/li>
&lt;li>&lt;a href="../resource-exhaustion-symptom-vs-holder/">#252 配額耗盡的症狀落在申請最頻繁的元件、成因在持有最久的那個&lt;/a>：同屬「錯誤產物與正確產物外觀相同」的家族，本卡沒有涵蓋解釋這一層。本卡的對象是分析邏輯的輸出（合法標籤、無錯誤值），#252 的對象是診斷者當場串出來的解釋——它的每個組成事實都經得起查證，因此自洽度無法作為正確性的證據，與正確的解釋在讀起來的感受上不可區分。兩者的處置方向相同：引入一個獨立於產物本身的來源來驗，本卡是總量守恆檢查，#252 是系統保存的持有者紀錄。&lt;/li>
&lt;li>&lt;a href="../judgment-rules-must-specify-their-trace/">#243 要求執行者做判定的規則，要一併規定判定留下什麼痕跡&lt;/a>：本卡的「條件式常數要寫下來源」是它在自動化判定上的形態。#243 處理人做的判定沒有痕跡就不可複驗，本卡處理程式做的判定沒有記下前提就不可追溯——兩者都要求把判定所依賴的東西留在產物裡，而不是留在做判定的人腦中。&lt;/li>
&lt;li>&lt;a href="../review-miss-diagnose-design-vs-execution-gap/">#153 漏抓先分 design gap 與 execution gap&lt;/a>：發現統計算錯時的第一個分流。本卡描述的是設計落差（分析邏輯的涵蓋範圍沒跟上資料模型），而它容易被誤診成執行落差（「這次忘了更新公式」）——誤診的代價是修好這一次、下一次新增形狀時重演。&lt;/li>
&lt;/ul>
&lt;h2 id="跟三個相鄰工程機制的差別">跟三個相鄰工程機制的差別&lt;/h2>
&lt;p>這張卡描述的現象與三個既有機制相鄰，界線值得說清楚，否則它讀起來像是它們的重述。&lt;/p>
&lt;p>&lt;strong>型別系統&lt;/strong>處理的是型別層的形狀，而本卡的形狀在語意層。一個列舉多出一個成員、一個事件型別欄多出一個值，型別完全沒變（都是字串），而依賴「這一欄只有兩種值」的計數邏輯已經失效。任何型別系統都接不住這一類。&lt;/p>
&lt;p>&lt;strong>Schema migration&lt;/strong> 覆蓋寫入端——建表、加欄、回填。慣例上它不覆蓋衍生的分析邏輯，因為那些散落在報表、儀表板與臨時查詢裡，不屬於 schema 的定義範圍。本卡的貢獻正是指出這條 checklist 停在 write path。&lt;/p>
&lt;p>&lt;strong>API 版本相容性&lt;/strong>最接近，兩者都在處理「生產者變了、消費者沒跟上」。差別在於 API 有一個明確的介面可以掛版號、有兩方各自的發布節奏、有 deprecation 期。本卡的情境是生產者與消費者是&lt;strong>同一個人、隔幾天、沒有任何介面&lt;/strong>——沒有版號可掛，也沒有第二方會抱怨。&lt;/p></description><content:encoded><![CDATA[<h2 id="論述基礎與限制">論述基礎與限制</h2>
<p>本卡從一套自建流量統計的資料判讀抽出，同一個錯誤在兩天內發生兩次、形狀完全相同。</p>
<p>第一次：原本的資料模型是每次瀏覽產生一列。判斷「這個 session 只看了一頁」的分類公式寫成 <code>COUNTIFS(session欄, 當列session) = 1</code>。後來為了量停留時間，加入離開事件，每次瀏覽變成一進一離兩列。公式沒有報錯，但 <code>= 1</code> 這個條件從此永遠不成立——每次瀏覽至少兩列。「單頁即走」這個分類從那一刻起恆為零，而零看起來就像「沒有這種流量」。</p>
<p>第二次：修正版公式開頭加了「非進入事件的列一律留白」，避免同一次瀏覽被計數兩次。這個修正在「每次瀏覽都有進入事件」的前提下正確。真實資料裡出現了第三種形狀：某些訪客只有離開事件、沒有進入事件（接收端的執行紀錄證實那個時間點沒收到任何請求，成因未確認）。這類列被開頭那個條件整批留白，一整類流量在統計裡完全不存在。</p>
<p>兩次都不是語法錯誤，也不是資料錯誤。公式合法、輸入正確、輸出是合法的分類標籤，只有分佈變了。</p>
<p>證據強度要說清楚，因為它與本卡的主張層級不對稱。觀測是兩次、來自同一個系統、同一位作者、同一週之內——這個規模不足以支持任何比例宣稱，也不足以支持「這類錯誤有多常見」。<strong>而核心原則不靠這兩次觀測成立</strong>：條件式編碼了未宣告的資料形狀假設，這是從條件式的性質推導出來的，兩次事件是它的例示而非樣本。分辨這兩層很重要，否則讀者會誤以為原則的可信度等於 n=2。</p>
<p>限制：本卡談的是資料形狀增加——新增事件型別、新增來源、新增狀態。欄位改名或型別變更在<strong>有型別檢查的環境</strong>會產生錯誤而自己現形，因此不在此列；在試算表這類沒有型別的環境它們同樣是靜默的（改欄位標題不會讓依範圍參照的公式報錯，數字被存成文字會讓求和悄悄少算），那些情形適用本卡的同一套修法。</p>
<hr>
<h2 id="核心原則">核心原則</h2>
<p><strong>分析邏輯裡的每一個條件式都編碼了一個對資料形狀的假設，而那個假設沒有住址。</strong> 資料多出一種形狀時，條件式仍然合法執行、仍然回傳值，只是它回答的問題已經和寫下它時不是同一個。</p>
<p>不可見有三個成分。</p>
<p><strong>假設不寫在任何地方。</strong> <code>COUNTIFS(...) = 1</code> 這個式子裡的「1」承載的是「一次瀏覽產生一列」這個資料模型事實。這個事實不在公式旁邊、不在欄位定義裡、不在文件裡——它只存在於寫下公式那一刻的心智模型中，而心智模型不會隨資料模型一起更新。</p>
<p><strong>錯誤的輸出與正確的輸出外觀相同。</strong> 分類邏輯回傳的仍然是合法標籤，沒有錯誤值、沒有型別衝突、沒有任何執行期的異常。要察覺它算錯，得先有一個獨立來源知道「這裡本來應該有多少」——而分析邏輯存在的理由，正是因為沒有那個獨立來源。</p>
<p><strong>變更的動機讓副作用更難聯想。</strong> 新增資料形狀通常是為了收集更多資訊，動機是「知道更多」；副作用卻是某些既有統計悄悄歸零或翻倍。歸零最危險——它與「這一類根本不存在」在數字上完全不可區分，這是 <a href="../lint-scope-must-be-explicit-fact/">#221</a> 描述的同一種訊號同構，只是發生在分析層而非檢查層。</p>
<hr>
<h2 id="修法">修法</h2>
<p><strong>資料模型新增一種形狀時，把下游分析邏輯的重算當成同一次變更的一部分。</strong> 分析邏輯是資料模型的下游依賴，和欄位定義、寫入程式碼屬於同一次變更的範圍。延後處理會失去對照：之後回頭看時，看到的數字已經是新公式算出來的，沒有東西可以比對。</p>
<p><strong>讓未涵蓋的資料可見，而非留白。</strong> 分類邏輯加一個 catch-all 分支，把沒被任何條件接住的列標成「未分類」。留白與「不適用」在視覺上不可區分，「未分類」則是一個會被看見的標籤——它不需要有人事先知道漏了什麼，只需要有人看一眼分佈。</p>
<p><strong>用總量守恆檢查涵蓋。</strong> 分類後各類數量的總和與原始列數的差額應該為零，差額不為零代表有形狀沒被涵蓋。這個檢查的成本是一條公式，抓的卻是所有「沒想到還有這種資料」的情形——它不要求預先列舉漏掉了什麼，這正是它比逐條檢視有效的地方。</p>
<p><strong>條件式裡的常數要寫下它的來源。</strong> 在 <code>= 1</code> 旁註明「假設每次瀏覽一列」。這讓資料模型變更時的反向搜尋成為可能：改事件模型的人可以搜尋「每次瀏覽」，找到所有依賴這個事實的地方。沒有這行註記時，那些依賴分散在各個條件式裡，只能靠記憶列舉。</p>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>某個分類的計數恆為零，且沒有人記得它上次非零是什麼時候——先檢查它的條件式是否還與當前資料形狀相符，再檢查現實中是否真的沒有那一類資料。</p>
<p>資料模型變更後，總量沒變但各分類的比例大幅移動——移動可能是真實的，也可能是某條件式換了語意，兩者的區別要靠對照舊公式在舊資料上的結果。</p>
<p>新增了一種事件、一種來源或一種狀態，而變更範圍裡沒有任何一處提到報表或公式——這代表下游依賴尚未被盤點，不代表下游沒有依賴。</p>
<hr>
<h2 id="跟其他原則的關係">跟其他原則的關係</h2>
<ul>
<li><a href="../lint-scope-must-be-explicit-fact/">#221 檢查規則的作用域要顯式列舉</a>：兩者共享「零與未涵蓋不可區分」這個核心結構，作用層不同。#221 說的是檢查工具的納管範圍（未納管目錄的零 error 與合規目錄的零 error 訊號相同），本卡說的是分析邏輯的涵蓋範圍（未被條件接住的資料與不存在的資料計數相同）。處置方向也同構——都是把隱含的範圍變成顯式的列舉。</li>
<li><a href="../self-audit-detection-method-must-match-rule-type/">#232 自審 sweep 的偵測方法要對齊規則類型</a>：#232 講偵測方法要看得見違反形態才可信，本卡是它在資料層的對應——分析邏輯要涵蓋得到某種資料形狀，它的計數才可信。兩者都在回答「這個 clean / 這個零，是通過還是沒被看到」。</li>
<li><a href="../proportional-scaling-is-not-space-allocation/">#228 等比縮放不管空間分配</a>：#228 指出工具的保證只涵蓋它工作的那一層，問題發生在別層時工具的靜默是「沒看到」而非「沒有」。本卡把同一件事推到分析邏輯：公式的保證只涵蓋它被寫下時的資料形狀，新形狀出現時它的合法輸出是「沒看到」而非「沒有」。</li>
<li><a href="../declared-composition-is-not-performed-composition/">#239 宣告的組合不等於執行的組合</a>：兩者都描述「存在」與「作用」之間的落差。#239 的落差在規則層（規則寫下了但沒被執行），本卡的落差在邏輯層（條件式執行了但已經不對應原本的問題）。本卡多一個條件——落差是由另一次正確的變更引入的，因此在變更當下沒有任何人做錯事。</li>
<li><a href="../resource-exhaustion-symptom-vs-holder/">#252 配額耗盡的症狀落在申請最頻繁的元件、成因在持有最久的那個</a>：同屬「錯誤產物與正確產物外觀相同」的家族，本卡沒有涵蓋解釋這一層。本卡的對象是分析邏輯的輸出（合法標籤、無錯誤值），#252 的對象是診斷者當場串出來的解釋——它的每個組成事實都經得起查證，因此自洽度無法作為正確性的證據，與正確的解釋在讀起來的感受上不可區分。兩者的處置方向相同：引入一個獨立於產物本身的來源來驗，本卡是總量守恆檢查，#252 是系統保存的持有者紀錄。</li>
<li><a href="../judgment-rules-must-specify-their-trace/">#243 要求執行者做判定的規則，要一併規定判定留下什麼痕跡</a>：本卡的「條件式常數要寫下來源」是它在自動化判定上的形態。#243 處理人做的判定沒有痕跡就不可複驗，本卡處理程式做的判定沒有記下前提就不可追溯——兩者都要求把判定所依賴的東西留在產物裡，而不是留在做判定的人腦中。</li>
<li><a href="../review-miss-diagnose-design-vs-execution-gap/">#153 漏抓先分 design gap 與 execution gap</a>：發現統計算錯時的第一個分流。本卡描述的是設計落差（分析邏輯的涵蓋範圍沒跟上資料模型），而它容易被誤診成執行落差（「這次忘了更新公式」）——誤診的代價是修好這一次、下一次新增形狀時重演。</li>
</ul>
<h2 id="跟三個相鄰工程機制的差別">跟三個相鄰工程機制的差別</h2>
<p>這張卡描述的現象與三個既有機制相鄰，界線值得說清楚，否則它讀起來像是它們的重述。</p>
<p><strong>型別系統</strong>處理的是型別層的形狀，而本卡的形狀在語意層。一個列舉多出一個成員、一個事件型別欄多出一個值，型別完全沒變（都是字串），而依賴「這一欄只有兩種值」的計數邏輯已經失效。任何型別系統都接不住這一類。</p>
<p><strong>Schema migration</strong> 覆蓋寫入端——建表、加欄、回填。慣例上它不覆蓋衍生的分析邏輯，因為那些散落在報表、儀表板與臨時查詢裡，不屬於 schema 的定義範圍。本卡的貢獻正是指出這條 checklist 停在 write path。</p>
<p><strong>API 版本相容性</strong>最接近，兩者都在處理「生產者變了、消費者沒跟上」。差別在於 API 有一個明確的介面可以掛版號、有兩方各自的發布節奏、有 deprecation 期。本卡的情境是生產者與消費者是<strong>同一個人、隔幾天、沒有任何介面</strong>——沒有版號可掛，也沒有第二方會抱怨。</p>
]]></content:encoded></item></channel></rss>