<?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>Naming on Tarragon</title><link>https://tarrragon.github.io/blog/tags/naming/</link><description>Recent content in Naming 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/naming/index.xml" rel="self" type="application/rss+xml"/><item><title>行為事件設計</title><link>https://tarrragon.github.io/blog/monitoring/08-business-analytics/behavior-event-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/08-business-analytics/behavior-event-design/</guid><description>&lt;p>行為事件是使用者操作的結構化記錄，每一筆事件回答「誰、在什麼時候、做了什麼、結果如何」。行為分析的品質上限由事件設計決定 — 事件粒度太粗無法回答細節問題，事件粒度太細讓儲存和查詢成本失控。&lt;/p>
&lt;h2 id="事件命名">事件命名&lt;/h2>
&lt;p>行為事件的命名遵循 &lt;code>namespace.action&lt;/code> 格式（&lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">模組一 事件命名規範&lt;/a>）。行為分析場景對命名的額外要求是：同一個 funnel 內的事件要能用 namespace 前綴篩選。&lt;/p>
&lt;p>例：註冊流程的事件用共同前綴 &lt;code>signup&lt;/code>：&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">signup.page.view 使用者看到註冊頁
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">signup.form.submit 使用者送出表單
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">signup.email.verify 使用者點擊驗證信連結
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">signup.complete 註冊完成&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用 &lt;code>signup.*&lt;/code> 就能篩選出整個註冊流程的事件，不需要事先知道每一步的完整名稱。&lt;/p>
&lt;h2 id="屬性設計">屬性設計&lt;/h2>
&lt;p>每個事件除了名稱，還帶有屬性（properties / parameters）描述事件的 context。屬性分成三層：&lt;/p>
&lt;h3 id="通用屬性每個事件都有">通用屬性（每個事件都有）&lt;/h3>
&lt;ul>
&lt;li>&lt;code>timestamp&lt;/code>：事件發生的時間（UTC，毫秒精度）&lt;/li>
&lt;li>&lt;code>session_id&lt;/code>：當次使用的 session 識別碼&lt;/li>
&lt;li>&lt;code>user_id&lt;/code>：使用者識別碼（去識別化後，見 &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七&lt;/a>）&lt;/li>
&lt;li>&lt;code>platform&lt;/code>：iOS / Android / Web&lt;/li>
&lt;li>&lt;code>app_version&lt;/code>：app 版本號&lt;/li>
&lt;/ul>
&lt;h3 id="事件類型屬性同類事件共有">事件類型屬性（同類事件共有）&lt;/h3>
&lt;ul>
&lt;li>頁面瀏覽事件：&lt;code>page_name&lt;/code>、&lt;code>referrer&lt;/code>&lt;/li>
&lt;li>按鈕點擊事件：&lt;code>button_id&lt;/code>、&lt;code>button_text&lt;/code>&lt;/li>
&lt;li>搜尋事件：&lt;code>query&lt;/code>、&lt;code>result_count&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="事件專屬屬性特定事件才有">事件專屬屬性（特定事件才有）&lt;/h3>
&lt;ul>
&lt;li>&lt;code>signup.form.submit&lt;/code>：&lt;code>form_method&lt;/code>（email / Google / Apple）&lt;/li>
&lt;li>&lt;code>purchase.complete&lt;/code>：&lt;code>amount&lt;/code>、&lt;code>currency&lt;/code>、&lt;code>product_id&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>屬性設計的判斷標準是：這個屬性是否用於回答一個分析問題。「註冊方式的轉換率差異」需要 &lt;code>form_method&lt;/code> 屬性；如果沒有這個分析問題，就不需要這個屬性。&lt;/p>
&lt;h2 id="funnel-定義">Funnel 定義&lt;/h2>
&lt;p>Funnel 是一連串有順序的事件，代表使用者完成一個目標的步驟。Funnel 定義在事件設計階段完成 — 決定哪些事件構成一個 funnel、順序是什麼、每步之間的最大時間間隔。&lt;/p>
&lt;p>定義一個 funnel 需要：&lt;/p>
&lt;p>&lt;strong>步驟清單&lt;/strong>：funnel 包含哪些事件，順序是什麼。&lt;/p>
&lt;p>&lt;strong>時間窗口&lt;/strong>：步驟之間的最大間隔。使用者在步驟 A 之後 30 天才做步驟 B，是否算在同一個 funnel 內？時間窗口的設定取決於業務場景 — 電商結帳 funnel 通常是 30 分鐘，SaaS onboarding funnel 可能是 7 天。&lt;/p>
&lt;p>&lt;strong>完成條件&lt;/strong>：什麼算「完成」funnel。到達最後一步即完成，還是需要特定屬性值（&lt;code>purchase.complete&lt;/code> 且 &lt;code>status = success&lt;/code>）。&lt;/p>
&lt;h2 id="過度收集的成本">過度收集的成本&lt;/h2>
&lt;p>行為事件收集的邊界是「能回答已知的分析問題」。收集超出分析需求的事件有三個成本：&lt;/p>
&lt;p>&lt;strong>儲存成本&lt;/strong>：每個事件佔一行 JSONL。高頻事件（每次滾動、每次 hover）的資料量遠大於低頻事件（按鈕點擊、頁面瀏覽）。&lt;/p>
&lt;p>&lt;strong>隱私風險&lt;/strong>：收集的事件越多，包含可識別個人行為模式的風險越高（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七 資安與隱私&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>噪音&lt;/strong>：分析時需要從大量事件中篩選出有意義的模式。事件越多，訊噪比越低。&lt;/p>
&lt;p>設計好的行為事件直接成為 &lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">Funnel analysis&lt;/a> 的輸入 — funnel 的每一步對應一個行為事件。行為事件在四類事件分類中屬於 Event 類，完整的分類定義見&lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">模組一 四類事件定義&lt;/a>。收集行為事件前必須完成&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">去識別化&lt;/a> — 使用者行為模式本身就是可識別資訊。&lt;/p></description><content:encoded><![CDATA[<p>行為事件是使用者操作的結構化記錄，每一筆事件回答「誰、在什麼時候、做了什麼、結果如何」。行為分析的品質上限由事件設計決定 — 事件粒度太粗無法回答細節問題，事件粒度太細讓儲存和查詢成本失控。</p>
<h2 id="事件命名">事件命名</h2>
<p>行為事件的命名遵循 <code>namespace.action</code> 格式（<a href="/blog/monitoring/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">模組一 事件命名規範</a>）。行為分析場景對命名的額外要求是：同一個 funnel 內的事件要能用 namespace 前綴篩選。</p>
<p>例：註冊流程的事件用共同前綴 <code>signup</code>：</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">signup.page.view          使用者看到註冊頁
</span></span><span class="line"><span class="ln">2</span><span class="cl">signup.form.submit        使用者送出表單
</span></span><span class="line"><span class="ln">3</span><span class="cl">signup.email.verify       使用者點擊驗證信連結
</span></span><span class="line"><span class="ln">4</span><span class="cl">signup.complete           註冊完成</span></span></code></pre></div><p>用 <code>signup.*</code> 就能篩選出整個註冊流程的事件，不需要事先知道每一步的完整名稱。</p>
<h2 id="屬性設計">屬性設計</h2>
<p>每個事件除了名稱，還帶有屬性（properties / parameters）描述事件的 context。屬性分成三層：</p>
<h3 id="通用屬性每個事件都有">通用屬性（每個事件都有）</h3>
<ul>
<li><code>timestamp</code>：事件發生的時間（UTC，毫秒精度）</li>
<li><code>session_id</code>：當次使用的 session 識別碼</li>
<li><code>user_id</code>：使用者識別碼（去識別化後，見 <a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七</a>）</li>
<li><code>platform</code>：iOS / Android / Web</li>
<li><code>app_version</code>：app 版本號</li>
</ul>
<h3 id="事件類型屬性同類事件共有">事件類型屬性（同類事件共有）</h3>
<ul>
<li>頁面瀏覽事件：<code>page_name</code>、<code>referrer</code></li>
<li>按鈕點擊事件：<code>button_id</code>、<code>button_text</code></li>
<li>搜尋事件：<code>query</code>、<code>result_count</code></li>
</ul>
<h3 id="事件專屬屬性特定事件才有">事件專屬屬性（特定事件才有）</h3>
<ul>
<li><code>signup.form.submit</code>：<code>form_method</code>（email / Google / Apple）</li>
<li><code>purchase.complete</code>：<code>amount</code>、<code>currency</code>、<code>product_id</code></li>
</ul>
<p>屬性設計的判斷標準是：這個屬性是否用於回答一個分析問題。「註冊方式的轉換率差異」需要 <code>form_method</code> 屬性；如果沒有這個分析問題，就不需要這個屬性。</p>
<h2 id="funnel-定義">Funnel 定義</h2>
<p>Funnel 是一連串有順序的事件，代表使用者完成一個目標的步驟。Funnel 定義在事件設計階段完成 — 決定哪些事件構成一個 funnel、順序是什麼、每步之間的最大時間間隔。</p>
<p>定義一個 funnel 需要：</p>
<p><strong>步驟清單</strong>：funnel 包含哪些事件，順序是什麼。</p>
<p><strong>時間窗口</strong>：步驟之間的最大間隔。使用者在步驟 A 之後 30 天才做步驟 B，是否算在同一個 funnel 內？時間窗口的設定取決於業務場景 — 電商結帳 funnel 通常是 30 分鐘，SaaS onboarding funnel 可能是 7 天。</p>
<p><strong>完成條件</strong>：什麼算「完成」funnel。到達最後一步即完成，還是需要特定屬性值（<code>purchase.complete</code> 且 <code>status = success</code>）。</p>
<h2 id="過度收集的成本">過度收集的成本</h2>
<p>行為事件收集的邊界是「能回答已知的分析問題」。收集超出分析需求的事件有三個成本：</p>
<p><strong>儲存成本</strong>：每個事件佔一行 JSONL。高頻事件（每次滾動、每次 hover）的資料量遠大於低頻事件（按鈕點擊、頁面瀏覽）。</p>
<p><strong>隱私風險</strong>：收集的事件越多，包含可識別個人行為模式的風險越高（<a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七 資安與隱私</a>）。</p>
<p><strong>噪音</strong>：分析時需要從大量事件中篩選出有意義的模式。事件越多，訊噪比越低。</p>
<p>設計好的行為事件直接成為 <a href="/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">Funnel analysis</a> 的輸入 — funnel 的每一步對應一個行為事件。行為事件在四類事件分類中屬於 Event 類，完整的分類定義見<a href="/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">模組一 四類事件定義</a>。收集行為事件前必須完成<a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">去識別化</a> — 使用者行為模式本身就是可識別資訊。</p>
]]></content:encoded></item><item><title>事件命名規範</title><link>https://tarrragon.github.io/blog/monitoring/01-mental-model/event-naming-convention/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/01-mental-model/event-naming-convention/</guid><description>&lt;p>事件命名的目的是讓事件可以被 grep、過濾和統計。統一的命名規範讓不同時期、不同開發者加入的事件能在同一個查詢框架中使用。&lt;/p>
&lt;h2 id="namespaceaction-格式">namespace.action 格式&lt;/h2>
&lt;p>每個事件名稱由兩部分組成：namespace（事件發生的模組或功能區域）和 action（發生了什麼）。用 &lt;code>.&lt;/code> 分隔。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">terminal.connect.start ← namespace: terminal.connect, action: start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">terminal.connect.done ← namespace: terminal.connect, action: &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">terminal.input.submit ← namespace: terminal.input, action: submit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">auth.biometric.success ← namespace: auth.biometric, action: success
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">auth.biometric.fallback ← namespace: auth.biometric, action: fallback
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">enrollment.qr.scan ← namespace: enrollment.qr, action: scan&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="namespace-層級">Namespace 層級&lt;/h3>
&lt;p>Namespace 的層級深度依功能結構而定。兩層通常足夠（&lt;code>terminal.connect&lt;/code>），三層用於需要進一步區分的場景（&lt;code>terminal.connect.ws&lt;/code>）。超過三層通常代表 namespace 設計過細，增加認知成本但不增加分析價值。&lt;/p>
&lt;h3 id="action-命名">Action 命名&lt;/h3>
&lt;p>Action 使用動詞（&lt;code>start&lt;/code>、&lt;code>submit&lt;/code>、&lt;code>scan&lt;/code>）或狀態（&lt;code>success&lt;/code>、&lt;code>failed&lt;/code>、&lt;code>timeout&lt;/code>）。同一組動作用配對的 action 名稱：&lt;code>start&lt;/code> / &lt;code>done&lt;/code>（成對的生命週期）、&lt;code>success&lt;/code> / &lt;code>failed&lt;/code>（結果分支）。&lt;/p>
&lt;p>避免在 action 中重複 namespace 的資訊。&lt;code>terminal.connect.terminal_connected&lt;/code> 中 &lt;code>terminal&lt;/code> 重複了；&lt;code>terminal.connect.done&lt;/code> 更簡潔。&lt;/p>
&lt;h2 id="命名一致性的工程價值">命名一致性的工程價值&lt;/h2>
&lt;h3 id="grep-友好">Grep 友好&lt;/h3>
&lt;p>統一的 namespace 結構讓開發者用 &lt;code>grep &amp;quot;terminal.connect&amp;quot;&lt;/code> 就能找到所有連線相關事件，不需要知道每個事件的完整名稱。&lt;/p>
&lt;h3 id="統計友好">統計友好&lt;/h3>
&lt;p>按 namespace 前綴分群統計。&lt;code>terminal.*&lt;/code> 的事件數量 = terminal 功能的使用頻率；&lt;code>auth.*&lt;/code> 的事件數量 = 認證觸發頻率。層級結構讓統計的粒度可以調整。&lt;/p>
&lt;h3 id="文件友好">文件友好&lt;/h3>
&lt;p>事件清單按 namespace 排列就是一份結構化的功能地圖。新加入的開發者讀事件清單就能理解系統有哪些功能模組。&lt;/p>
&lt;h2 id="和商業方案的命名對應">和商業方案的命名對應&lt;/h2>
&lt;p>不同的商業監控方案有各自的命名慣例。自架方案用 namespace.action 格式，接入商業方案時需要做對應。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>商業方案&lt;/th>
 &lt;th>命名慣例&lt;/th>
 &lt;th>對應方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>GA4&lt;/td>
 &lt;td>&lt;code>event_name&lt;/code> + parameters&lt;/td>
 &lt;td>namespace.action → &lt;code>event_name&lt;/code>，細節放 parameters&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sentry&lt;/td>
 &lt;td>transaction name + spans&lt;/td>
 &lt;td>namespace → transaction，action → span&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mixpanel&lt;/td>
 &lt;td>event name + properties&lt;/td>
 &lt;td>namespace.action → event name&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Datadog RUM&lt;/td>
 &lt;td>action name + view name&lt;/td>
 &lt;td>action → action name，namespace → view&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>對應時保持一個原則：自架方案的事件名稱是 source of truth，商業方案的名稱是它的映射。在自架方案中改名後，映射層跟著改；不要讓商業方案的命名反過來影響自架的命名結構。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>四類事件的定義 → &lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">四類事件的完整定義&lt;/a>&lt;/li>
&lt;li>從需求推導收集策略 → &lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/derive-collection-from-requirements/" data-link-title="從需求推導「該收集哪些事件」" data-link-desc="從 debug 需求、行為分析需求、效能需求、合規需求四個方向推導事件收集策略 — 避免「什麼都收」和「什麼都不收」">從需求推導「該收集哪些事件」&lt;/a>&lt;/li>
&lt;li>商業方案的完整比較 → &lt;a href="https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">模組六 商業方案比較&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>事件命名的目的是讓事件可以被 grep、過濾和統計。統一的命名規範讓不同時期、不同開發者加入的事件能在同一個查詢框架中使用。</p>
<h2 id="namespaceaction-格式">namespace.action 格式</h2>
<p>每個事件名稱由兩部分組成：namespace（事件發生的模組或功能區域）和 action（發生了什麼）。用 <code>.</code> 分隔。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">terminal.connect.start      ← namespace: terminal.connect, action: start
</span></span><span class="line"><span class="ln">2</span><span class="cl">terminal.connect.done       ← namespace: terminal.connect, action: <span class="k">done</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">terminal.input.submit       ← namespace: terminal.input, action: submit
</span></span><span class="line"><span class="ln">4</span><span class="cl">auth.biometric.success      ← namespace: auth.biometric, action: success
</span></span><span class="line"><span class="ln">5</span><span class="cl">auth.biometric.fallback     ← namespace: auth.biometric, action: fallback
</span></span><span class="line"><span class="ln">6</span><span class="cl">enrollment.qr.scan          ← namespace: enrollment.qr, action: scan</span></span></code></pre></div><h3 id="namespace-層級">Namespace 層級</h3>
<p>Namespace 的層級深度依功能結構而定。兩層通常足夠（<code>terminal.connect</code>），三層用於需要進一步區分的場景（<code>terminal.connect.ws</code>）。超過三層通常代表 namespace 設計過細，增加認知成本但不增加分析價值。</p>
<h3 id="action-命名">Action 命名</h3>
<p>Action 使用動詞（<code>start</code>、<code>submit</code>、<code>scan</code>）或狀態（<code>success</code>、<code>failed</code>、<code>timeout</code>）。同一組動作用配對的 action 名稱：<code>start</code> / <code>done</code>（成對的生命週期）、<code>success</code> / <code>failed</code>（結果分支）。</p>
<p>避免在 action 中重複 namespace 的資訊。<code>terminal.connect.terminal_connected</code> 中 <code>terminal</code> 重複了；<code>terminal.connect.done</code> 更簡潔。</p>
<h2 id="命名一致性的工程價值">命名一致性的工程價值</h2>
<h3 id="grep-友好">Grep 友好</h3>
<p>統一的 namespace 結構讓開發者用 <code>grep &quot;terminal.connect&quot;</code> 就能找到所有連線相關事件，不需要知道每個事件的完整名稱。</p>
<h3 id="統計友好">統計友好</h3>
<p>按 namespace 前綴分群統計。<code>terminal.*</code> 的事件數量 = terminal 功能的使用頻率；<code>auth.*</code> 的事件數量 = 認證觸發頻率。層級結構讓統計的粒度可以調整。</p>
<h3 id="文件友好">文件友好</h3>
<p>事件清單按 namespace 排列就是一份結構化的功能地圖。新加入的開發者讀事件清單就能理解系統有哪些功能模組。</p>
<h2 id="和商業方案的命名對應">和商業方案的命名對應</h2>
<p>不同的商業監控方案有各自的命名慣例。自架方案用 namespace.action 格式，接入商業方案時需要做對應。</p>
<table>
  <thead>
      <tr>
          <th>商業方案</th>
          <th>命名慣例</th>
          <th>對應方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GA4</td>
          <td><code>event_name</code> + parameters</td>
          <td>namespace.action → <code>event_name</code>，細節放 parameters</td>
      </tr>
      <tr>
          <td>Sentry</td>
          <td>transaction name + spans</td>
          <td>namespace → transaction，action → span</td>
      </tr>
      <tr>
          <td>Mixpanel</td>
          <td>event name + properties</td>
          <td>namespace.action → event name</td>
      </tr>
      <tr>
          <td>Datadog RUM</td>
          <td>action name + view name</td>
          <td>action → action name，namespace → view</td>
      </tr>
  </tbody>
</table>
<p>對應時保持一個原則：自架方案的事件名稱是 source of truth，商業方案的名稱是它的映射。在自架方案中改名後，映射層跟著改；不要讓商業方案的命名反過來影響自架的命名結構。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>四類事件的定義 → <a href="/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">四類事件的完整定義</a></li>
<li>從需求推導收集策略 → <a href="/blog/monitoring/01-mental-model/derive-collection-from-requirements/" data-link-title="從需求推導「該收集哪些事件」" data-link-desc="從 debug 需求、行為分析需求、效能需求、合規需求四個方向推導事件收集策略 — 避免「什麼都收」和「什麼都不收」">從需求推導「該收集哪些事件」</a></li>
<li>商業方案的完整比較 → <a href="/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">模組六 商業方案比較</a></li>
</ul>
]]></content:encoded></item><item><title>「名義 integration test」的識別與修正</title><link>https://tarrragon.github.io/blog/testing/01-test-strategy-layers/nominal-integration-test/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/01-test-strategy-layers/nominal-integration-test/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/nominal-integration-test/" data-link-title="名義 Integration Test" data-link-desc="名稱含 integration 但核心依賴全用 fake 的 test，驗證內部狀態機而非真實服務互動">名義 integration test&lt;/a> 是指 test 的名稱或檔案路徑包含「integration」或「端對端」，但實際上核心外部依賴全部被 fake 替換，驗證的是內部狀態機而非真實服務互動。這類 test 的核心問題是命名造成的認知偏差：團隊以為「integration test 有寫」，實際上協議層完全沒被驗證。它們驗證的邏輯可能完全正確 — 問題在命名，不在品質。&lt;/p>
&lt;h2 id="辨識特徵">辨識特徵&lt;/h2>
&lt;p>app_tunnel 的 &lt;code>connection_flow_test.dart&lt;/code> 是具體案例。檔名標題是端對端整合測試，但內部使用了三個核心替身：&lt;code>FakeWebSocketChannel&lt;/code>、&lt;code>FakeBiometricService&lt;/code>、&lt;code>InMemoryCredentialRepository&lt;/code>（&lt;a href="https://tarrragon.github.io/blog/testing/cases/auth-handshake-missing-mock-blindspot/" data-link-title="T.C2 Auth handshake 邏輯缺失被 FakeWebSocketChannel 遮蔽" data-link-desc="ttyd 連線後需要發送 auth token JSON frame 完成認證，整個邏輯未實作 — FakeWebSocketChannel 的 ready 立即完成不需認證，test 永遠看到連線成功">T.C2&lt;/a>）。&lt;/p>
&lt;p>名義 integration test 有三個共同特徵可用來辨識。&lt;/p>
&lt;h3 id="特徵一核心外部依賴全替換">特徵一：核心外部依賴全替換&lt;/h3>
&lt;p>Integration test 的價值在於驗證程式碼與外部系統的互動邊界。如果所有外部依賴都被 fake 取代，test 驗證的實際上是「假設外部系統行為符合開發者預期，內部邏輯是否正確」。這和 unit test 的差別只在 scope 大小 — 多個內部元件一起測 — 不在驗證對象的本質。&lt;/p>
&lt;p>判斷方式：列出 test 的所有依賴注入點，計算有多少外部服務被替換成 fake。如果 100% 的外部依賴都是 fake，這個 test 不驗證任何真實互動。&lt;/p>
&lt;h3 id="特徵二沒有真實的-io-操作">特徵二：沒有真實的 I/O 操作&lt;/h3>
&lt;p>真正的 integration test 會產生真實的網路連線、讀寫真實的檔案、或呼叫真實的 API endpoint。名義 integration test 的所有 I/O 都在 process 內部完成 — &lt;code>StreamController&lt;/code> 替代網路 stream，&lt;code>Map&amp;lt;String, String&amp;gt;&lt;/code> 替代資料庫，&lt;code>Future.value()&lt;/code> 替代非同步 I/O。&lt;/p>
&lt;p>這些替身讓 test 執行速度快、結果穩定，但代價是完全跳過了 I/O 邊界上的所有行為差異。&lt;/p>
&lt;h3 id="特徵三沒有環境前置條件">特徵三：沒有環境前置條件&lt;/h3>
&lt;p>真正的 integration test 需要外部環境準備：啟動服務、建立連線、準備測試資料。名義 integration test 的 &lt;code>setUp()&lt;/code> 只建立 fake 物件，不啟動任何外部程序，不需要網路，可以在任何環境下執行。&lt;/p>
&lt;p>環境前置條件的缺席是一個實用的快速判斷訊號。如果 &lt;code>setUp()&lt;/code> 裡沒有 &lt;code>docker compose up&lt;/code>、&lt;code>Process.start&lt;/code>、&lt;code>HttpClient.connect&lt;/code> 之類的操作，這個 test 很可能不接觸真實外部服務。&lt;/p>
&lt;h2 id="名義-integration-test-造成的認知偏差">名義 integration test 造成的認知偏差&lt;/h2>
&lt;p>名義 integration test 的技術問題可以修正（改名或補寫真實 integration test），但它造成的認知偏差更難修正。&lt;/p>
&lt;p>當團隊看到 test suite 包含「integration test」資料夾且全部通過，決策者的推論是「integration 已經驗證過了」。這個推論在名義 integration test 下是錯的 — 協議層和環境層完全沒被驗證 — 但決策者沒有動機去檢查 test 的內部實作。&lt;/p>
&lt;p>app_tunnel 的 11 個 &lt;code>connection_flow_test&lt;/code> 全過，開發者合理認為「連線流程的整合測試已通過」。實際上這 11 個 test 驗證的是 &lt;code>ConnectionManager&lt;/code> 的內部狀態機在各種情境下的轉換正確性（斷線重連、錯誤處理、狀態回報），不是「和 ttyd 的連線流程是否正確」。Auth handshake 缺失直到實機測試才被發現。&lt;/p>
&lt;h2 id="修正策略">修正策略&lt;/h2>
&lt;h3 id="修正命名">修正命名&lt;/h3>
&lt;p>最低成本的修正是讓 test 名稱反映真實驗證對象。命名改動不影響 test 本身的價值 — 這些 test 驗證內部狀態機的邏輯仍然有用 — 只是消除命名造成的認知偏差。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>原名稱&lt;/th>
 &lt;th>修正後名稱&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>connection_flow_test&lt;/td>
 &lt;td>connection_state_machine_test&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;tr>
 &lt;td>integration_test/&lt;/td>
 &lt;td>state_machine_test/&lt;/td>
 &lt;td>資料夾名稱影響團隊對 test 覆蓋範圍的認知&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="補寫真實-integration-test">補寫真實 integration test&lt;/h3>
&lt;p>命名修正只消除誤解，不補上缺失的驗證層。如果服務的協議互動是關鍵路徑（連線、認證、資料交換），需要補寫對真實服務的 protocol integration test。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/testing/knowledge-cards/nominal-integration-test/" data-link-title="名義 Integration Test" data-link-desc="名稱含 integration 但核心依賴全用 fake 的 test，驗證內部狀態機而非真實服務互動">名義 integration test</a> 是指 test 的名稱或檔案路徑包含「integration」或「端對端」，但實際上核心外部依賴全部被 fake 替換，驗證的是內部狀態機而非真實服務互動。這類 test 的核心問題是命名造成的認知偏差：團隊以為「integration test 有寫」，實際上協議層完全沒被驗證。它們驗證的邏輯可能完全正確 — 問題在命名，不在品質。</p>
<h2 id="辨識特徵">辨識特徵</h2>
<p>app_tunnel 的 <code>connection_flow_test.dart</code> 是具體案例。檔名標題是端對端整合測試，但內部使用了三個核心替身：<code>FakeWebSocketChannel</code>、<code>FakeBiometricService</code>、<code>InMemoryCredentialRepository</code>（<a href="/blog/testing/cases/auth-handshake-missing-mock-blindspot/" data-link-title="T.C2 Auth handshake 邏輯缺失被 FakeWebSocketChannel 遮蔽" data-link-desc="ttyd 連線後需要發送 auth token JSON frame 完成認證，整個邏輯未實作 — FakeWebSocketChannel 的 ready 立即完成不需認證，test 永遠看到連線成功">T.C2</a>）。</p>
<p>名義 integration test 有三個共同特徵可用來辨識。</p>
<h3 id="特徵一核心外部依賴全替換">特徵一：核心外部依賴全替換</h3>
<p>Integration test 的價值在於驗證程式碼與外部系統的互動邊界。如果所有外部依賴都被 fake 取代，test 驗證的實際上是「假設外部系統行為符合開發者預期，內部邏輯是否正確」。這和 unit test 的差別只在 scope 大小 — 多個內部元件一起測 — 不在驗證對象的本質。</p>
<p>判斷方式：列出 test 的所有依賴注入點，計算有多少外部服務被替換成 fake。如果 100% 的外部依賴都是 fake，這個 test 不驗證任何真實互動。</p>
<h3 id="特徵二沒有真實的-io-操作">特徵二：沒有真實的 I/O 操作</h3>
<p>真正的 integration test 會產生真實的網路連線、讀寫真實的檔案、或呼叫真實的 API endpoint。名義 integration test 的所有 I/O 都在 process 內部完成 — <code>StreamController</code> 替代網路 stream，<code>Map&lt;String, String&gt;</code> 替代資料庫，<code>Future.value()</code> 替代非同步 I/O。</p>
<p>這些替身讓 test 執行速度快、結果穩定，但代價是完全跳過了 I/O 邊界上的所有行為差異。</p>
<h3 id="特徵三沒有環境前置條件">特徵三：沒有環境前置條件</h3>
<p>真正的 integration test 需要外部環境準備：啟動服務、建立連線、準備測試資料。名義 integration test 的 <code>setUp()</code> 只建立 fake 物件，不啟動任何外部程序，不需要網路，可以在任何環境下執行。</p>
<p>環境前置條件的缺席是一個實用的快速判斷訊號。如果 <code>setUp()</code> 裡沒有 <code>docker compose up</code>、<code>Process.start</code>、<code>HttpClient.connect</code> 之類的操作，這個 test 很可能不接觸真實外部服務。</p>
<h2 id="名義-integration-test-造成的認知偏差">名義 integration test 造成的認知偏差</h2>
<p>名義 integration test 的技術問題可以修正（改名或補寫真實 integration test），但它造成的認知偏差更難修正。</p>
<p>當團隊看到 test suite 包含「integration test」資料夾且全部通過，決策者的推論是「integration 已經驗證過了」。這個推論在名義 integration test 下是錯的 — 協議層和環境層完全沒被驗證 — 但決策者沒有動機去檢查 test 的內部實作。</p>
<p>app_tunnel 的 11 個 <code>connection_flow_test</code> 全過，開發者合理認為「連線流程的整合測試已通過」。實際上這 11 個 test 驗證的是 <code>ConnectionManager</code> 的內部狀態機在各種情境下的轉換正確性（斷線重連、錯誤處理、狀態回報），不是「和 ttyd 的連線流程是否正確」。Auth handshake 缺失直到實機測試才被發現。</p>
<h2 id="修正策略">修正策略</h2>
<h3 id="修正命名">修正命名</h3>
<p>最低成本的修正是讓 test 名稱反映真實驗證對象。命名改動不影響 test 本身的價值 — 這些 test 驗證內部狀態機的邏輯仍然有用 — 只是消除命名造成的認知偏差。</p>
<table>
  <thead>
      <tr>
          <th>原名稱</th>
          <th>修正後名稱</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>connection_flow_test</td>
          <td>connection_state_machine_test</td>
          <td>測的是狀態機邏輯，不是真實連線流程</td>
      </tr>
      <tr>
          <td>端對端整合測試</td>
          <td>狀態機分支覆蓋</td>
          <td>測的是分支覆蓋，不是端對端</td>
      </tr>
      <tr>
          <td>integration_test/</td>
          <td>state_machine_test/</td>
          <td>資料夾名稱影響團隊對 test 覆蓋範圍的認知</td>
      </tr>
  </tbody>
</table>
<h3 id="補寫真實-integration-test">補寫真實 integration test</h3>
<p>命名修正只消除誤解，不補上缺失的驗證層。如果服務的協議互動是關鍵路徑（連線、認證、資料交換），需要補寫對真實服務的 protocol integration test。</p>
<p>補寫的判斷原則不在本章展開 — 見 <a href="/blog/testing/01-test-strategy-layers/when-protocol-integration-test/" data-link-title="判斷原則：什麼時候需要 protocol integration test" data-link-desc="從服務架構特徵判斷是否需要 protocol integration test 的決策流程 — 協議複雜度、mock 寬鬆度、失敗靜默度三個維度">判斷原則：什麼時候需要 protocol integration test</a>。</p>
<h3 id="在-test-檔案內標明依賴替換清單">在 test 檔案內標明依賴替換清單</h3>
<p>在 test 檔案的頂部註釋中列出所有被 fake 取代的依賴，讓後續讀者不需要逐行追蹤就能判斷這個 test 的驗證邊界。</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="c1">// Faked dependencies: WebSocketChannel, BiometricService, CredentialRepository
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// Verifies: ConnectionManager state machine transitions
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="n">Does</span> <span class="n">NOT</span> <span class="nl">verify:</span> <span class="n">real</span> <span class="n">WS</span> <span class="n">protocol</span><span class="p">,</span> <span class="n">auth</span> <span class="n">handshake</span><span class="p">,</span> <span class="n">biometric</span> <span class="n">hardware</span></span></span></code></pre></div><h2 id="下一步路由">下一步路由</h2>
<ul>
<li>判斷是否需要補寫真實 integration test → <a href="/blog/testing/01-test-strategy-layers/when-protocol-integration-test/" data-link-title="判斷原則：什麼時候需要 protocol integration test" data-link-desc="從服務架構特徵判斷是否需要 protocol integration test 的決策流程 — 協議複雜度、mock 寬鬆度、失敗靜默度三個維度">判斷原則：什麼時候需要 protocol integration test</a></li>
<li>Mock 遮蔽機制的完整分析 → <a href="/blog/testing/01-test-strategy-layers/mock-masking-mechanism/" data-link-title="Mock 遮蔽機制分析" data-link-desc="Mock 在 API 層、協議層、環境層之間製造的結構性盲區 — 斷裂點在哪、為什麼 mock 無法也不應該模擬協議行為">Mock 遮蔽機制分析</a></li>
<li>想建 protocol integration test → <a href="/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試</a></li>
</ul>
]]></content:encoded></item><item><title>名義 Integration Test</title><link>https://tarrragon.github.io/blog/testing/knowledge-cards/nominal-integration-test/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/knowledge-cards/nominal-integration-test/</guid><description>&lt;p>名義 integration test 的核心概念是「test 標題或路徑包含 integration，但所有外部依賴都被 fake 替換，實際驗證的是內部邏輯而非真實服務互動」。它的問題在命名造成的認知偏差 — 團隊以為 integration 已驗證，實際上協議層完全沒被覆蓋。可先對照 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/mock-masking/" data-link-title="Mock 遮蔽" data-link-desc="mock 模擬 API 層但不模擬協議層，造成的結構性驗證盲區">mock 遮蔽&lt;/a>和 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration test&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>名義 integration test 在 test 分類中介於 unit test 和真正的 integration test 之間。它的 scope 比 unit test 大（多個內部元件一起測），但驗證對象和 unit test 相同（程式碼內部邏輯）。它和 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/mock-masking/" data-link-title="Mock 遮蔽" data-link-desc="mock 模擬 API 層但不模擬協議層，造成的結構性驗證盲區">mock 遮蔽&lt;/a>的關係是：名義 integration test 用 mock 替換所有外部依賴，mock 遮蔽了協議層行為，而 test 名稱讓團隊以為這些行為已被驗證。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>辨識名義 integration test 的三個特徵：核心外部依賴 100% 被 fake 取代、沒有真實的 I/O 操作（網路、檔案、資料庫）、&lt;code>setUp()&lt;/code> 不需要啟動外部程序或建立網路連線。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>修正策略分兩步：改名（讓 test 名稱反映真實驗證對象，如 &lt;code>connection_state_machine_test&lt;/code>）和補寫（如果協議互動是關鍵路徑，補寫對真實服務的 &lt;a href="https://tarrragon.github.io/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration test&lt;/a>）。在 test 檔案頂部標明被 fake 取代的依賴清單，讓後續讀者快速判斷驗證邊界。&lt;/p></description><content:encoded><![CDATA[<p>名義 integration test 的核心概念是「test 標題或路徑包含 integration，但所有外部依賴都被 fake 替換，實際驗證的是內部邏輯而非真實服務互動」。它的問題在命名造成的認知偏差 — 團隊以為 integration 已驗證，實際上協議層完全沒被覆蓋。可先對照 <a href="/blog/testing/knowledge-cards/mock-masking/" data-link-title="Mock 遮蔽" data-link-desc="mock 模擬 API 層但不模擬協議層，造成的結構性驗證盲區">mock 遮蔽</a>和 <a href="/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration test</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>名義 integration test 在 test 分類中介於 unit test 和真正的 integration test 之間。它的 scope 比 unit test 大（多個內部元件一起測），但驗證對象和 unit test 相同（程式碼內部邏輯）。它和 <a href="/blog/testing/knowledge-cards/mock-masking/" data-link-title="Mock 遮蔽" data-link-desc="mock 模擬 API 層但不模擬協議層，造成的結構性驗證盲區">mock 遮蔽</a>的關係是：名義 integration test 用 mock 替換所有外部依賴，mock 遮蔽了協議層行為，而 test 名稱讓團隊以為這些行為已被驗證。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>辨識名義 integration test 的三個特徵：核心外部依賴 100% 被 fake 取代、沒有真實的 I/O 操作（網路、檔案、資料庫）、<code>setUp()</code> 不需要啟動外部程序或建立網路連線。</p>
<h2 id="設計責任">設計責任</h2>
<p>修正策略分兩步：改名（讓 test 名稱反映真實驗證對象，如 <code>connection_state_machine_test</code>）和補寫（如果協議互動是關鍵路徑，補寫對真實服務的 <a href="/blog/testing/knowledge-cards/protocol-integration-test/" data-link-title="Protocol Integration Test" data-link-desc="驗證程式碼和真實外部服務之間的協議互動是否正確的 test 層級">protocol integration test</a>）。在 test 檔案頂部標明被 fake 取代的依賴清單，讓後續讀者快速判斷驗證邊界。</p>
]]></content:encoded></item><item><title>Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂</title><link>https://tarrragon.github.io/blog/report/naming-as-iterated-artifact/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/naming-as-iterated-artifact/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>第一次寫的名字幾乎都不對 — 不是因為命名能力不夠、是因為&lt;strong>第一版命名只能基於「寫的當下看到的 context」&lt;/strong>、而正確的名字需要看到「未來所有 call-site / grep 結果 / 重構場景」。&lt;/p>
&lt;p>命名的正確設計是 &lt;strong>iterated artifact&lt;/strong>：寫 → re-read → 改 → 再 re-read → 收斂。每輪用不同 frame：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>輪&lt;/th>
 &lt;th>Frame&lt;/th>
 &lt;th>抓什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>第一版&lt;/td>
 &lt;td>把概念變字串&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>Grep-ability&lt;/td>
 &lt;td>能單次 grep 命中嗎？跟其他 entity 名字不撞嗎？&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>Cross-call-site&lt;/td>
 &lt;td>從 caller 角度看、名字暗示的契約對嗎？&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>Impl 洩漏檢查&lt;/td>
 &lt;td>名字洩漏了 impl 細節嗎？換 impl 名字會錯嗎？&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每輪可能 catch 到「上一輪沒看到」的問題、迫使重命名。&lt;strong>接受重命名是命名工作的常態、不是失敗&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼第一版幾乎不對">為什麼第一版幾乎不對&lt;/h2>
&lt;p>寫第一版時、認知資源都在「概念是什麼」、剩下的給命名只夠：&lt;/p>
&lt;ul>
&lt;li>看到當前 function 在做的事 → 命名只反映當前&lt;/li>
&lt;li>不知道未來會有 N 個 call-site → 沒考慮一致性&lt;/li>
&lt;li>不知道未來會有 grep / refactor → 沒考慮 unique-ness&lt;/li>
&lt;li>不知道未來會換 impl → 命名容易洩漏現在的 impl 細節&lt;/li>
&lt;/ul>
&lt;p>第一版命名是&lt;strong>對「現在的 context」過度擬合&lt;/strong>。下一輪 review 換 frame 才能看到擬合方向之外。&lt;/p>
&lt;hr>
&lt;h2 id="四輪-review-的具體-checklist">四輪 review 的具體 checklist&lt;/h2>
&lt;h3 id="輪-1第一版">輪 1：第一版&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 名字反映「做什麼 / 是什麼」、不是「怎麼做」&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 動詞 / 名詞符合語言慣例（function 動詞、value 名詞）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 不超過 4 個單字（長 ≠ 清楚）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 跑得到 next step、不在這輪糾結&lt;/li>
&lt;/ul>
&lt;h3 id="輪-2grep-ability">輪 2：Grep-ability&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;code>grep -r &amp;quot;&amp;lt;name&amp;gt;&amp;quot;&lt;/code> 能命中目標、不會被別的 entity 蓋過&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 跟 framework / library reserved name 不撞（避免 &lt;code>data&lt;/code>、&lt;code>type&lt;/code>、&lt;code>value&lt;/code> 等過泛）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 名字不是其他名字的子字串（&lt;code>get&lt;/code> 會匹配 &lt;code>getName&lt;/code> &lt;code>getUser&lt;/code>&amp;hellip;）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 中英混合場景下、英文部分能 grep（不要用 &lt;code>處理器handler&lt;/code> 這種 mixed）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 縮寫慎用（&lt;code>usr&lt;/code> &lt;code>cfg&lt;/code> &lt;code>mgr&lt;/code> 增加 grep 失敗率）&lt;/li>
&lt;/ul>
&lt;h3 id="輪-3cross-call-site-一致性">輪 3：Cross-call-site 一致性&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 從 caller 角度看、名字暗示的契約對嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 跟同 module 其他類似 entity 命名格式一致嗎？（&lt;code>getUser&lt;/code> vs &lt;code>fetchUser&lt;/code> 不該混用）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 同一個概念在不同 file 用同名嗎？（不該 &lt;code>userId&lt;/code> / &lt;code>user_id&lt;/code> / &lt;code>uid&lt;/code> 三個並存）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 動詞時態一致嗎？（&lt;code>fetched&lt;/code> vs &lt;code>fetching&lt;/code> vs &lt;code>fetch&lt;/code> 對應狀態 / 動作 / 命令、不該混用）&lt;/li>
&lt;/ul>
&lt;h3 id="輪-4impl-洩漏檢查">輪 4：Impl 洩漏檢查&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 名字含 impl 細節嗎？（&lt;code>fetchUserViaSql&lt;/code> ≠ &lt;code>fetchUser&lt;/code>、後者較好）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 換 impl 後名字還對嗎？（&lt;code>cacheGetUser&lt;/code> 改成走 DB 後名字錯了）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 名字洩漏 data structure 細節嗎？（&lt;code>userArray&lt;/code> ≠ &lt;code>users&lt;/code>、後者不綁 array）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 介面層名字 vs 實作層名字區分嗎？（介面用「做什麼」、實作用「怎麼做」可加細節）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="套用到不同命名場景">套用到不同命名場景&lt;/h2>
&lt;h3 id="變數--函式">變數 / 函式&lt;/h3>
&lt;p>完整跑 1-4 輪。&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>第一次寫的名字幾乎都不對 — 不是因為命名能力不夠、是因為<strong>第一版命名只能基於「寫的當下看到的 context」</strong>、而正確的名字需要看到「未來所有 call-site / grep 結果 / 重構場景」。</p>
<p>命名的正確設計是 <strong>iterated artifact</strong>：寫 → re-read → 改 → 再 re-read → 收斂。每輪用不同 frame：</p>
<table>
  <thead>
      <tr>
          <th>輪</th>
          <th>Frame</th>
          <th>抓什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>第一版</td>
          <td>把概念變字串</td>
      </tr>
      <tr>
          <td>2</td>
          <td>Grep-ability</td>
          <td>能單次 grep 命中嗎？跟其他 entity 名字不撞嗎？</td>
      </tr>
      <tr>
          <td>3</td>
          <td>Cross-call-site</td>
          <td>從 caller 角度看、名字暗示的契約對嗎？</td>
      </tr>
      <tr>
          <td>4</td>
          <td>Impl 洩漏檢查</td>
          <td>名字洩漏了 impl 細節嗎？換 impl 名字會錯嗎？</td>
      </tr>
  </tbody>
</table>
<p>每輪可能 catch 到「上一輪沒看到」的問題、迫使重命名。<strong>接受重命名是命名工作的常態、不是失敗</strong>。</p>
<hr>
<h2 id="為什麼第一版幾乎不對">為什麼第一版幾乎不對</h2>
<p>寫第一版時、認知資源都在「概念是什麼」、剩下的給命名只夠：</p>
<ul>
<li>看到當前 function 在做的事 → 命名只反映當前</li>
<li>不知道未來會有 N 個 call-site → 沒考慮一致性</li>
<li>不知道未來會有 grep / refactor → 沒考慮 unique-ness</li>
<li>不知道未來會換 impl → 命名容易洩漏現在的 impl 細節</li>
</ul>
<p>第一版命名是<strong>對「現在的 context」過度擬合</strong>。下一輪 review 換 frame 才能看到擬合方向之外。</p>
<hr>
<h2 id="四輪-review-的具體-checklist">四輪 review 的具體 checklist</h2>
<h3 id="輪-1第一版">輪 1：第一版</h3>
<ul>
<li><input disabled="" type="checkbox"> 名字反映「做什麼 / 是什麼」、不是「怎麼做」</li>
<li><input disabled="" type="checkbox"> 動詞 / 名詞符合語言慣例（function 動詞、value 名詞）</li>
<li><input disabled="" type="checkbox"> 不超過 4 個單字（長 ≠ 清楚）</li>
<li><input disabled="" type="checkbox"> 跑得到 next step、不在這輪糾結</li>
</ul>
<h3 id="輪-2grep-ability">輪 2：Grep-ability</h3>
<ul>
<li><input disabled="" type="checkbox"> <code>grep -r &quot;&lt;name&gt;&quot;</code> 能命中目標、不會被別的 entity 蓋過</li>
<li><input disabled="" type="checkbox"> 跟 framework / library reserved name 不撞（避免 <code>data</code>、<code>type</code>、<code>value</code> 等過泛）</li>
<li><input disabled="" type="checkbox"> 名字不是其他名字的子字串（<code>get</code> 會匹配 <code>getName</code> <code>getUser</code>&hellip;）</li>
<li><input disabled="" type="checkbox"> 中英混合場景下、英文部分能 grep（不要用 <code>處理器handler</code> 這種 mixed）</li>
<li><input disabled="" type="checkbox"> 縮寫慎用（<code>usr</code> <code>cfg</code> <code>mgr</code> 增加 grep 失敗率）</li>
</ul>
<h3 id="輪-3cross-call-site-一致性">輪 3：Cross-call-site 一致性</h3>
<ul>
<li><input disabled="" type="checkbox"> 從 caller 角度看、名字暗示的契約對嗎？</li>
<li><input disabled="" type="checkbox"> 跟同 module 其他類似 entity 命名格式一致嗎？（<code>getUser</code> vs <code>fetchUser</code> 不該混用）</li>
<li><input disabled="" type="checkbox"> 同一個概念在不同 file 用同名嗎？（不該 <code>userId</code> / <code>user_id</code> / <code>uid</code> 三個並存）</li>
<li><input disabled="" type="checkbox"> 動詞時態一致嗎？（<code>fetched</code> vs <code>fetching</code> vs <code>fetch</code> 對應狀態 / 動作 / 命令、不該混用）</li>
</ul>
<h3 id="輪-4impl-洩漏檢查">輪 4：Impl 洩漏檢查</h3>
<ul>
<li><input disabled="" type="checkbox"> 名字含 impl 細節嗎？（<code>fetchUserViaSql</code> ≠ <code>fetchUser</code>、後者較好）</li>
<li><input disabled="" type="checkbox"> 換 impl 後名字還對嗎？（<code>cacheGetUser</code> 改成走 DB 後名字錯了）</li>
<li><input disabled="" type="checkbox"> 名字洩漏 data structure 細節嗎？（<code>userArray</code> ≠ <code>users</code>、後者不綁 array）</li>
<li><input disabled="" type="checkbox"> 介面層名字 vs 實作層名字區分嗎？（介面用「做什麼」、實作用「怎麼做」可加細節）</li>
</ul>
<hr>
<h2 id="套用到不同命名場景">套用到不同命名場景</h2>
<h3 id="變數--函式">變數 / 函式</h3>
<p>完整跑 1-4 輪。</p>
<p>額外注意：</p>
<ul>
<li><strong>作用域</strong> — 越窄作用域越可短（loop counter <code>i</code>、close-up var <code>tmp</code>）；越寬作用域越要明確</li>
<li><strong>類型暗示</strong> — boolean 用 <code>is</code> / <code>has</code> / <code>should</code> 開頭</li>
</ul>
<h3 id="檔名--module">檔名 / module</h3>
<p>跑 1-4 + 加：</p>
<ul>
<li><strong>層級表達</strong> — 檔名能否反映在 directory 結構中的位置？</li>
<li><strong>避免 <code>utils</code> / <code>helpers</code> / <code>common</code></strong> — 這類是「不知該叫什麼」的訊號、強制再過一次輪 1-4</li>
</ul>
<h3 id="url-slug--route">URL slug / route</h3>
<p>跑 1-4 + 加：</p>
<ul>
<li><strong>SEO</strong> — 跟 search query 的 substring match 對齊（<a href="../search-engine-matching-mode-mismatch/">#73 search 匹配模式</a>）</li>
<li><strong>kebab-case 一致</strong></li>
<li><strong>不含 stop words</strong>（<code>the</code>、<code>a</code>、<code>is</code>、<code>of</code>、<code>with</code>、<code>and</code>）— 跟搜尋引擎 stemming 對齊</li>
</ul>
<h3 id="api-endpoint--db-column">API endpoint / DB column</h3>
<p>跑 1-4 + 加：</p>
<ul>
<li><strong>跨 service 一致性</strong> — 同一概念在 client / server / DB 用同名（避免 <code>user_id</code> / <code>userId</code> / <code>uid</code> 跨 layer 不一致）</li>
<li><strong>不可變更性</strong> — DB column / API endpoint 改名成本極高、輪 1-4 多跑幾次值得</li>
</ul>
<hr>
<h2 id="反模式放棄重命名">反模式：放棄重命名</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「先這樣、之後再改」</td>
          <td><a href="../external-trigger-for-high-roi-work/">#72 結構性跳過</a> — 永遠不改</td>
      </tr>
      <tr>
          <td>「重命名 PR 風險高、別做」</td>
          <td>累積成 cognitive debt、後續 onboarding / debug 成本爆炸</td>
      </tr>
      <tr>
          <td>「IDE 會自動重命名、不用想清楚」</td>
          <td>IDE 改不到 doc / commit / chat 引用</td>
      </tr>
      <tr>
          <td>用 <code>data</code> <code>value</code> <code>type</code> <code>info</code> <code>obj</code> 含糊命名</td>
          <td>grep 失敗率高、自帶 false-match</td>
      </tr>
      <tr>
          <td>用語言不一致的 <code>處理 handler</code></td>
          <td>中英混雜、grep 兩邊都失敗</td>
      </tr>
      <tr>
          <td><code>tempVar1</code> <code>tempVar2</code> 流水號</td>
          <td>看不出是什麼、純佔位</td>
      </tr>
      <tr>
          <td><code>getUserById</code> 名字洩漏 query strategy</td>
          <td>換成 cache hit 後名字錯了</td>
      </tr>
      <tr>
          <td>複數同義詞並存（<code>fetch</code> / <code>get</code> / <code>load</code> / <code>retrieve</code>）</td>
          <td>caller 不知選哪個</td>
      </tr>
      <tr>
          <td>介面命名洩漏 impl（<code>HashMapUserStore</code>）</td>
          <td>impl 換 RedisStore 後 caller 跟著改</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="何時可以跳輪">何時可以跳輪</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>可跳輪</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Loop counter / 即時 close-up var</td>
          <td>只跑輪 1</td>
      </tr>
      <tr>
          <td>Test code 內部 helper</td>
          <td>跑輪 1 + 4</td>
      </tr>
      <tr>
          <td>Temporary script / one-off</td>
          <td>1 + 2</td>
      </tr>
      <tr>
          <td>跨 team API / DB schema</td>
          <td><strong>每輪都跑、跑兩遍</strong></td>
      </tr>
      <tr>
          <td>Public library / SDK</td>
          <td><strong>每輪都跑、跑兩遍</strong></td>
      </tr>
      <tr>
          <td>Production-facing URL / endpoint</td>
          <td><strong>不可跳、改名成本極高</strong></td>
      </tr>
  </tbody>
</table>
<p>兩極：作用域越窄越可省、跨邊界 / public 越要 multi-pass。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../writing-multi-pass-review/">#83 Writing 的 multi-pass review</a></td>
          <td>本卡是 #83 輪 4 在 naming 場景的特化</td>
      </tr>
      <tr>
          <td><a href="../literal-interception-vs-behavioral-refinement/">#82 字面攔截 vs 行為精煉</a></td>
          <td>命名 lint（max length、case style）只擋字面、grep-ability / 一致性 / impl 洩漏靠 multi-pass review</td>
      </tr>
      <tr>
          <td><a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度</a></td>
          <td>第一版命名是「容易寫」、不是「對齊意圖」、需要重命名</td>
      </tr>
      <tr>
          <td><a href="../search-engine-matching-mode-mismatch/">#73 search 匹配模式</a></td>
          <td>URL slug 的命名要跟 search 預期匹配模式對齊</td>
      </tr>
      <tr>
          <td><a href="../single-source-of-truth/">#44 SSOT</a></td>
          <td>同概念跨 layer 用同名 = naming SSOT、不該允許多版本同義</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>第一次想到的名字直接用了</td>
          <td>跑輪 2-4、預期會改</td>
      </tr>
      <tr>
          <td><code>data</code> <code>type</code> <code>value</code> <code>info</code> <code>obj</code> 出現</td>
          <td>含糊命名、強制重新命</td>
      </tr>
      <tr>
          <td><code>utils</code> / <code>helpers</code> / <code>common</code> module</td>
          <td>「不知該叫什麼」訊號、重新分類</td>
      </tr>
      <tr>
          <td>Grep 命中太多無關結果</td>
          <td>名字太短 / 太泛、重命名加 prefix</td>
      </tr>
      <tr>
          <td>Caller code 看 callsite 不知契約</td>
          <td>介面名字洩漏不夠、補強或改名</td>
      </tr>
      <tr>
          <td>重構後類型 / impl 換了名字沒換</td>
          <td>命名洩漏 impl、重命名</td>
      </tr>
      <tr>
          <td>同概念出現 ≥ 2 個名字</td>
          <td>違反 SSOT、選一個改另一個</td>
      </tr>
      <tr>
          <td>重命名 PR 被 reject「沒必要」</td>
          <td>文化沒接受 naming 是 iterated、補 reviewer education</td>
      </tr>
  </tbody>
</table>
<p><strong>核心</strong>：命名是 <strong>iterated artifact</strong>、不是 single-shot 動作。第一版基於狹窄 context 幾乎必錯。<strong>接受 N 輪 review 跟 K 次重命名是常態</strong>、命名品質會提升一個量級。試圖一次寫對 = 第一版 ship 出去 = 後續長期付 cognitive 成本。</p>
]]></content:encoded></item><item><title>集合命名用角色、不內嵌數量：「核心七問」的七是成員數的 derivation、加一問就全面失真</title><link>https://tarrragon.github.io/blog/report/name-collections-by-role-not-count/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/name-collections-by-role-not-count/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>集合型結構（問題清單、階段序列、原則組、支柱組）的命名&lt;strong>只承載角色與層級、把成員數量留給清單自己呈現&lt;/strong>。「核心問題」這個名稱承受任意的成員增減；「核心七問」把當前成員數烤進名字、加一問、七就變八：名稱本身先失真、散落各處的引用跟著全部過期。「次要問題」「撞牆階段」「防護底線」同理 — 角色詞完成分層、數量歸清單。&lt;/p>
&lt;p>區分核心問題跟次要問題、靠的是「核心 / 次要」這組角色詞；讀者選擇讀哪一組、不需要先知道組內有幾個成員。數字在名稱裡有真實的讀者側價值 — 記憶掛鉤與完整性提示（「五原則我只想起四個、漏了一個」）— 但這個價值在活集合下被漂移風險壓過、只有凍結集合能無風險收割它、這也是品牌名刻意用數字的原因；路由本身、角色詞已經完成。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼數量入名比編號引用更深一層">為什麼數量入名比編號引用更深一層&lt;/h2>
&lt;h3 id="數量是-membership-的-derivation">數量是 membership 的 derivation&lt;/h3>
&lt;p>一個集合的成員數由「目前有哪些成員」決定 — 跟章節編號由「前面排了幾章」決定同構、都是會隨演進變動的衍生值。差別在寄生位置：編號寄生在&lt;strong>引用句&lt;/strong>、數量寄生在&lt;strong>名稱本身&lt;/strong>。名稱是整個知識系統裡被複製最多次的字串（標題、引用、索引、目錄、對話、commit message 都在複製它）、缺陷跟著名稱繁殖到每一個出現點。&lt;/p>
&lt;h3 id="它讓語意標題是穩定錨的前提失效">它讓「語意標題是穩定錨」的前提失效&lt;/h3>
&lt;p>引用該錨在語意標題、因為標題被假設是這個單位的 fact。「核心七問」打破這個假設：標題有一半是語意（核心問題）、一半是 derivation（七）。成員一變、想守住名實一致就得全面改名、改名又回到「散落引用逐處修」的老路；想省事不改名、就留下一個說謊的名字（標題寫七問、實際有八問）、比編號錯位更糟 — 錯在系統的權威命名層。&lt;/p>
&lt;h3 id="失真的兩條路都有代價">失真的兩條路都有代價&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>成員變動後的選擇&lt;/th>
 &lt;th>代價&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>全面改名&lt;/td>
 &lt;td>名稱出現過的每一處（標題 / 引用 / 索引 / 歷史對話的延續）都要修、等同一次結構重排&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>保留舊名&lt;/td>
 &lt;td>名實不符常態化 — 讀者數了一下發現是八問、開始懷疑文件其他部分的可信度&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="反模式與修法">反模式與修法&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>反模式&lt;/th>
 &lt;th>修法&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &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;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;tr>
 &lt;td>五個定錨問題&lt;/td>
 &lt;td>定錨問題&lt;/td>
 &lt;td>行內描述也一樣、數量讓清單自己呈現&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>寫作時的判斷：命名一個集合時、問「這個數字提供了角色詞沒提供的資訊嗎？」— 答案幾乎都是否。讀者需要知道數量的時刻、是看到清單本身的時刻、清單天然自帶數量。&lt;/p>
&lt;h3 id="邊界三種數字可以留">邊界：三種數字可以留&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>外部凍結的品牌名&lt;/strong>：SOLID 五原則、OWASP Top 10、WRAP 四步驟 — 數量由發布方凍結、是名稱 fact 的一部分、跟引用 RFC 段號同理。&lt;/li>
&lt;li>&lt;strong>數字是概念內容本身&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">兩次門檻&lt;/a> 的二是規則的閾值、不是成員數 — 改了二就是改了概念、這種數字承載語意、該留。&lt;/li>
&lt;li>&lt;strong>緊鄰清單的行內計數&lt;/strong>：「確認三件事：」後面直接跟三條列 — 數字跟清單同視野、改清單時順手改數字、漂移會立刻被看見。風險低、但仍是小負債、清單就在下面時「確認下列事項：」更省。&lt;/li>
&lt;li>&lt;strong>內部宣告凍結的集合&lt;/strong>：團隊把某個方法論名稱當內部品牌用、可以明文宣告凍結後收割記憶價值 — 代價比凍結編號更嚴：凍結編號還能往後加、凍結數量連加都不能加、成員增減等於名稱重新談判。&lt;/li>
&lt;/ol>
&lt;p>判別線是「這個數字是 fact 還是 derivation」：發布方凍結的、概念內容本身的是 fact；內部活集合的成員數是 derivation、不入名。&lt;/p>
&lt;hr>
&lt;h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/reference-by-semantic-title-not-number/" data-link-title="引用章節用語意標題、不用位置編號：編號是結構排列的 derivation、會隨版本漂移" data-link-desc="跨段落、跨檔引用結構單位（章節 / 階段 / 條列項）時、引用語意標題（副標題）、不引用位置編號（Stage 3、第 5 章、第 3 點）。編號是「目前結構排列」的 derivation、不是 fact；結構重排時編號全部位移、引用點不會報錯、而是 silent 指向錯的內容 — 比 broken link 更難偵測。標題的存在意義就是承載可被引用的語意。是 #44 SSoT 在結構引用維度的實例、#93 identifier-as-fact 家族的 sibling、#84 命名承載語意的引用面延伸。">#155 引用章節用語意標題、不用位置編號&lt;/a>：直接 sibling、分工在管線的兩端 — #155 修引用端（錨點選什麼）、本卡修命名端（錨點本身怎麼長）。#155 假設語意標題是穩定 fact、本卡負責讓這個假設成立：count-bearing 標題是「一半 fact 一半 derivation」的混合體、先淨化命名、#155 的引用紀律才有穩定的錨可用。實證：#155 卡初版自己用「見核心七問」當正面範例、修引用端時沒發現錨點內嵌數量。兩卡查的是不同層：引用端的檢查抓不到命名端的缺陷、反過來也一樣。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/single-source-of-truth/" data-link-title="Single Source of Truth：值的住址只能有一處" data-link-desc="同一個值（CSS token、視覺基準、runtime 量測）的權威來源只能有一個位置 — 多源時會分歧、會漏改、會讓讀者不知道哪個生效。本文是 #3 / #26 / #27 三篇實作的共同抽象。">#44 Single Source of Truth&lt;/a>：成員數量的 truth 在清單本身（數一下就有）；把數量寫進名稱是把這個 derivation 複製成第二個源、且這個源被嵌進最高頻複製的字串裡：是 SSoT 違反裡擴散速度最快的形態。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">#84 Naming 是 iterated artifact&lt;/a>：本卡給 #84 的命名 review 加一個檢查維度 — 數量入名是「第一版命名基於當下狹窄 context」的典型產物：寫名字的當下成員剛好七個、七看起來是這個集合的屬性；cross-time 檢驗（成員會不會變）才暴露它是快照。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關&lt;/a>：「七問」「六階段」順口、有節奏感、好記 — 正是便利驅動的選擇；意圖對齊的名稱（核心問題）平淡但誠實。便利的代價延遲到第一次成員變動才結算。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="觸發-case">觸發 case&lt;/h2>
&lt;p>同一份多階段訪談 skill、同一天內兩次命中：&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>集合型結構（問題清單、階段序列、原則組、支柱組）的命名<strong>只承載角色與層級、把成員數量留給清單自己呈現</strong>。「核心問題」這個名稱承受任意的成員增減；「核心七問」把當前成員數烤進名字、加一問、七就變八：名稱本身先失真、散落各處的引用跟著全部過期。「次要問題」「撞牆階段」「防護底線」同理 — 角色詞完成分層、數量歸清單。</p>
<p>區分核心問題跟次要問題、靠的是「核心 / 次要」這組角色詞；讀者選擇讀哪一組、不需要先知道組內有幾個成員。數字在名稱裡有真實的讀者側價值 — 記憶掛鉤與完整性提示（「五原則我只想起四個、漏了一個」）— 但這個價值在活集合下被漂移風險壓過、只有凍結集合能無風險收割它、這也是品牌名刻意用數字的原因；路由本身、角色詞已經完成。</p>
<hr>
<h2 id="為什麼數量入名比編號引用更深一層">為什麼數量入名比編號引用更深一層</h2>
<h3 id="數量是-membership-的-derivation">數量是 membership 的 derivation</h3>
<p>一個集合的成員數由「目前有哪些成員」決定 — 跟章節編號由「前面排了幾章」決定同構、都是會隨演進變動的衍生值。差別在寄生位置：編號寄生在<strong>引用句</strong>、數量寄生在<strong>名稱本身</strong>。名稱是整個知識系統裡被複製最多次的字串（標題、引用、索引、目錄、對話、commit message 都在複製它）、缺陷跟著名稱繁殖到每一個出現點。</p>
<h3 id="它讓語意標題是穩定錨的前提失效">它讓「語意標題是穩定錨」的前提失效</h3>
<p>引用該錨在語意標題、因為標題被假設是這個單位的 fact。「核心七問」打破這個假設：標題有一半是語意（核心問題）、一半是 derivation（七）。成員一變、想守住名實一致就得全面改名、改名又回到「散落引用逐處修」的老路；想省事不改名、就留下一個說謊的名字（標題寫七問、實際有八問）、比編號錯位更糟 — 錯在系統的權威命名層。</p>
<h3 id="失真的兩條路都有代價">失真的兩條路都有代價</h3>
<table>
  <thead>
      <tr>
          <th>成員變動後的選擇</th>
          <th>代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全面改名</td>
          <td>名稱出現過的每一處（標題 / 引用 / 索引 / 歷史對話的延續）都要修、等同一次結構重排</td>
      </tr>
      <tr>
          <td>保留舊名</td>
          <td>名實不符常態化 — 讀者數了一下發現是八問、開始懷疑文件其他部分的可信度</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="反模式與修法">反模式與修法</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>修法</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心七問</td>
          <td>核心問題</td>
          <td>角色詞（核心）已完成分層、七是冗餘的快照</td>
      </tr>
      <tr>
          <td>成長六階段</td>
          <td>規模成長撞牆階段</td>
          <td>階段數會隨教材演進增減</td>
      </tr>
      <tr>
          <td>四大支柱</td>
          <td>核心支柱（或直接「支柱」）</td>
          <td>「大」字結構強迫帶數量、整個棄用</td>
      </tr>
      <tr>
          <td>訪談流程（六階段）</td>
          <td>訪談流程</td>
          <td>流程有幾站、目錄自己會說</td>
      </tr>
      <tr>
          <td>五個定錨問題</td>
          <td>定錨問題</td>
          <td>行內描述也一樣、數量讓清單自己呈現</td>
      </tr>
  </tbody>
</table>
<p>寫作時的判斷：命名一個集合時、問「這個數字提供了角色詞沒提供的資訊嗎？」— 答案幾乎都是否。讀者需要知道數量的時刻、是看到清單本身的時刻、清單天然自帶數量。</p>
<h3 id="邊界三種數字可以留">邊界：三種數字可以留</h3>
<ol>
<li><strong>外部凍結的品牌名</strong>：SOLID 五原則、OWASP Top 10、WRAP 四步驟 — 數量由發布方凍結、是名稱 fact 的一部分、跟引用 RFC 段號同理。</li>
<li><strong>數字是概念內容本身</strong>：<a href="/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">兩次門檻</a> 的二是規則的閾值、不是成員數 — 改了二就是改了概念、這種數字承載語意、該留。</li>
<li><strong>緊鄰清單的行內計數</strong>：「確認三件事：」後面直接跟三條列 — 數字跟清單同視野、改清單時順手改數字、漂移會立刻被看見。風險低、但仍是小負債、清單就在下面時「確認下列事項：」更省。</li>
<li><strong>內部宣告凍結的集合</strong>：團隊把某個方法論名稱當內部品牌用、可以明文宣告凍結後收割記憶價值 — 代價比凍結編號更嚴：凍結編號還能往後加、凍結數量連加都不能加、成員增減等於名稱重新談判。</li>
</ol>
<p>判別線是「這個數字是 fact 還是 derivation」：發布方凍結的、概念內容本身的是 fact；內部活集合的成員數是 derivation、不入名。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<ul>
<li><a href="/blog/report/reference-by-semantic-title-not-number/" data-link-title="引用章節用語意標題、不用位置編號：編號是結構排列的 derivation、會隨版本漂移" data-link-desc="跨段落、跨檔引用結構單位（章節 / 階段 / 條列項）時、引用語意標題（副標題）、不引用位置編號（Stage 3、第 5 章、第 3 點）。編號是「目前結構排列」的 derivation、不是 fact；結構重排時編號全部位移、引用點不會報錯、而是 silent 指向錯的內容 — 比 broken link 更難偵測。標題的存在意義就是承載可被引用的語意。是 #44 SSoT 在結構引用維度的實例、#93 identifier-as-fact 家族的 sibling、#84 命名承載語意的引用面延伸。">#155 引用章節用語意標題、不用位置編號</a>：直接 sibling、分工在管線的兩端 — #155 修引用端（錨點選什麼）、本卡修命名端（錨點本身怎麼長）。#155 假設語意標題是穩定 fact、本卡負責讓這個假設成立：count-bearing 標題是「一半 fact 一半 derivation」的混合體、先淨化命名、#155 的引用紀律才有穩定的錨可用。實證：#155 卡初版自己用「見核心七問」當正面範例、修引用端時沒發現錨點內嵌數量。兩卡查的是不同層：引用端的檢查抓不到命名端的缺陷、反過來也一樣。</li>
<li><a href="/blog/report/single-source-of-truth/" data-link-title="Single Source of Truth：值的住址只能有一處" data-link-desc="同一個值（CSS token、視覺基準、runtime 量測）的權威來源只能有一個位置 — 多源時會分歧、會漏改、會讓讀者不知道哪個生效。本文是 #3 / #26 / #27 三篇實作的共同抽象。">#44 Single Source of Truth</a>：成員數量的 truth 在清單本身（數一下就有）；把數量寫進名稱是把這個 derivation 複製成第二個源、且這個源被嵌進最高頻複製的字串裡：是 SSoT 違反裡擴散速度最快的形態。</li>
<li><a href="/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">#84 Naming 是 iterated artifact</a>：本卡給 #84 的命名 review 加一個檢查維度 — 數量入名是「第一版命名基於當下狹窄 context」的典型產物：寫名字的當下成員剛好七個、七看起來是這個集合的屬性；cross-time 檢驗（成員會不會變）才暴露它是快照。</li>
<li><a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度跟意圖對齊反相關</a>：「七問」「六階段」順口、有節奏感、好記 — 正是便利驅動的選擇；意圖對齊的名稱（核心問題）平淡但誠實。便利的代價延遲到第一次成員變動才結算。</li>
</ul>
<hr>
<h2 id="觸發-case">觸發 case</h2>
<p>同一份多階段訪談 skill、同一天內兩次命中：</p>
<ol>
<li><strong>已發生</strong>：skill 初版有「四大支柱」、第二版需求調整後支柱變六個 — 名稱被迫改成「六大支柱」、所有提到四大支柱的地方同步修。改完的新名字仍然內嵌數量、下次支柱增減會原樣重演。</li>
<li><strong>被預見</strong>：第二版的「核心七問」「成長六階段」被指出同樣結構 — 核心問題若加一問、「七」就散落在標題、引用、索引、路由表裡等著逐處修。</li>
</ol>
<p>更深的訊號是第三點：當天稍早為了修「Stage 3」編號引用立了一張卡（引用端）、該卡自己用「見核心七問」當正面範例；在專注檢查引用端時、命名端的同型缺陷完全隱形。確認了這是獨立的檢查維度、不是引用卡的子情況。</p>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<ul>
<li>命名或標題出現「N 大 X」「N 問」「N 階段」「N 支柱」「N 原則」「N 步驟」且 X 是內部活集合時 — 抽掉數字、看角色詞夠不夠分層、幾乎都夠。</li>
<li>Review 掃描可用：<code>rg &quot;[一二三四五六七八九十0-9]+ ?(大|問|階段|支柱|原則|步驟|件事|個維度)&quot; --type md</code> — 掃出來的不全是病灶：外部凍結品牌與概念閾值是 fact、留；內部活集合的成員數是 derivation、改。</li>
<li>集合成員增減的 commit、檢查名稱是否還誠實 — 需要同步改名的話、這次改名時順便把數量抽掉、別讓下次再來一遍。</li>
</ul>
]]></content:encoded></item><item><title>語意錨用單一字串、同義雙名讓引用修復退回人腦對應</title><link>https://tarrragon.github.io/blog/report/semantic-anchor-single-string/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/semantic-anchor-single-string/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>一個結構單位的語意名稱&lt;strong>只能有一個 canonical 字串、所有引用使用同一字串&lt;/strong>。「決策收斂」跟「決策記錄 + scaffold 建議」都是語意名、都通過了「不用編號」的檢查 — 但兩個字串指同一個階段、語意引用的收益就漏了一半：工具掃描要掃兩個 pattern 才完整、漏配置一個就漏一半引用點；結構重排時、修復者要先在腦中建立「這兩個名字是同一個東西」的對應表、而這張表沒有寫在任何地方。&lt;/p>
&lt;p>語意引用的承諾是「錨點穩定、且機器與人都能單次比對」。同義雙名保住了穩定、丟掉了單次比對 — 等於把 fact 寫成兩種拼法。&lt;/p>
&lt;h2 id="為什麼雙名會自然發生">為什麼雙名會自然發生&lt;/h2>
&lt;p>雙名通常在不同檔案、不同時間寫下：標題在設計流程時取（描述產出物：「決策記錄 + scaffold 建議」）、引用在寫協作檔時取（描述階段角色：「決策收斂階段」）。每一次取名都在自己的當下語境裡合理、分裂要把兩個時間點的產物擺在一起才看得到 — 單檔 review 永遠只看到其中一個名字、所以雙名能安然通過所有單檔檢查。&lt;/p>
&lt;p>偵測需要跨檔視角：把每個結構單位的「標題語意半邊」列成清單、grep 全部引用句、比對引用字串是否落在清單內。引用字串語意對、字面不在清單內、就是雙名。&lt;/p>
&lt;h2 id="反模式與修法">反模式與修法&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>反模式&lt;/th>
 &lt;th>修法&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>標題「Stage 5：決策記錄 + scaffold 建議」、引用「決策收斂階段」&lt;/td>
 &lt;td>二選一、全 repo 統一（標題改「Stage 5：決策收斂（決策記錄 + scaffold 建議）」、引用維持「決策收斂」）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「domain / event 切分」與「領域切分」混用&lt;/td>
 &lt;td>取標題語意半邊為 canonical、其餘出現點改寫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>縮寫與全名並存當引用錨（「ops 階段」「操作維運階段」）&lt;/td>
 &lt;td>全名為 canonical、縮寫只出現在已建立對應的同段內&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>修法的判斷次序：canonical 字串取「標題的語意半邊」（標題是該單位的 fact 所在）；標題語意半邊太長不適合引用時、先改標題、再統一引用 — 標題改短是一次成本、引用各自簡寫是持續發散。&lt;/p>
&lt;h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/reference-by-semantic-title-not-number/" data-link-title="引用章節用語意標題、不用位置編號：編號是結構排列的 derivation、會隨版本漂移" data-link-desc="跨段落、跨檔引用結構單位（章節 / 階段 / 條列項）時、引用語意標題（副標題）、不引用位置編號（Stage 3、第 5 章、第 3 點）。編號是「目前結構排列」的 derivation、不是 fact；結構重排時編號全部位移、引用點不會報錯、而是 silent 指向錯的內容 — 比 broken link 更難偵測。標題的存在意義就是承載可被引用的語意。是 #44 SSoT 在結構引用維度的實例、#93 identifier-as-fact 家族的 sibling、#84 命名承載語意的引用面延伸。">#155 引用章節用語意標題、不用位置編號&lt;/a>：本卡補 #155 的缺口。#155 把錨點從編號換成語意名稱、預設了「語意名稱是單一的」；雙名讓 #155 的修法只完成一半 — 錨點穩定了、可比對性沒跟上。三卡合起來是完整的引用紀律：#155 管引用端（錨什麼）、#156 管命名端的內容（名稱不含 derivation）、本卡管命名端的唯一性（一個單位一個字串）。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/name-collections-by-role-not-count/" data-link-title="集合命名用角色、不內嵌數量：「核心七問」的七是成員數的 derivation、加一問就全面失真" data-link-desc="「核心七問」「成長六階段」「四大支柱」這類名稱把成員數量烤進名字裡 — 數量是集合當前成員的 derivation、不是集合的語意身分；成員增減時名稱失真、且名稱是被複製最多次的字串、缺陷隨每次引用繁殖。修法：命名只承載角色與層級（核心問題 / 次要問題 / 撞牆階段）、數量讓清單自己呈現。本卡是 #155 的命名端 sibling（#155 修引用端、本卡讓「語意標題是穩定錨」的前提真正成立）、#44 SSoT 在名稱內容的實例、#84 命名檢驗的數量維度。">#156 集合命名用角色、不內嵌數量&lt;/a>：sibling。#156 處理名稱「內容」的 fact 純度、本卡處理名稱「數量」的唯一性 — 一個名稱可以通過 #156（不含成員數）仍違反本卡（有同義變體）。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/single-source-of-truth/" data-link-title="Single Source of Truth：值的住址只能有一處" data-link-desc="同一個值（CSS token、視覺基準、runtime 量測）的權威來源只能有一個位置 — 多源時會分歧、會漏改、會讓讀者不知道哪個生效。本文是 #3 / #26 / #27 三篇實作的共同抽象。">#44 Single Source of Truth&lt;/a>：同一語意身分的兩個字串就是同一個 fact 的兩個源 — 比值的多源更隱蔽、因為兩個字串「語意上相等」、讀者看不出這是違規、只有工具比對才會痛。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">#84 Naming 是 iterated artifact&lt;/a>：#84 的輪 3 明文檢查「同一個概念在不同檔案用同名嗎」、判讀徵兆也列了「同概念出現兩個以上名字、選一個改另一個」— 本卡是這個檢查項在「結構單位的標題與引用」場景的應用：被檢查的從變數名換成章節 / 階段的語意錨、檢查動作相同。&lt;/li>
&lt;/ul>
&lt;h2 id="觸發-case">觸發 case&lt;/h2>
&lt;p>一份多階段訪談 skill 在一致性 audit 中被抓到兩處：階段標題寫「決策記錄 + scaffold 建議」、兩個協作檔的引用寫「決策收斂階段」；另一個階段的引用在「domain / event 切分」與「領域切分」之間混用。每個字串單獨檢查都合規（語意名、無編號、無數量）— audit reviewer 的原話是「語意可對上、但嚴格來說引用錨與標題語意半邊不同字串、下次重排時要靠人腦對應」。修法：標題語意半邊改成「決策收斂」、全 skill 引用統一；「領域切分」全部改寫為「domain / event 切分」。&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>一個結構單位的語意名稱<strong>只能有一個 canonical 字串、所有引用使用同一字串</strong>。「決策收斂」跟「決策記錄 + scaffold 建議」都是語意名、都通過了「不用編號」的檢查 — 但兩個字串指同一個階段、語意引用的收益就漏了一半：工具掃描要掃兩個 pattern 才完整、漏配置一個就漏一半引用點；結構重排時、修復者要先在腦中建立「這兩個名字是同一個東西」的對應表、而這張表沒有寫在任何地方。</p>
<p>語意引用的承諾是「錨點穩定、且機器與人都能單次比對」。同義雙名保住了穩定、丟掉了單次比對 — 等於把 fact 寫成兩種拼法。</p>
<h2 id="為什麼雙名會自然發生">為什麼雙名會自然發生</h2>
<p>雙名通常在不同檔案、不同時間寫下：標題在設計流程時取（描述產出物：「決策記錄 + scaffold 建議」）、引用在寫協作檔時取（描述階段角色：「決策收斂階段」）。每一次取名都在自己的當下語境裡合理、分裂要把兩個時間點的產物擺在一起才看得到 — 單檔 review 永遠只看到其中一個名字、所以雙名能安然通過所有單檔檢查。</p>
<p>偵測需要跨檔視角：把每個結構單位的「標題語意半邊」列成清單、grep 全部引用句、比對引用字串是否落在清單內。引用字串語意對、字面不在清單內、就是雙名。</p>
<h2 id="反模式與修法">反模式與修法</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>標題「Stage 5：決策記錄 + scaffold 建議」、引用「決策收斂階段」</td>
          <td>二選一、全 repo 統一（標題改「Stage 5：決策收斂（決策記錄 + scaffold 建議）」、引用維持「決策收斂」）</td>
      </tr>
      <tr>
          <td>「domain / event 切分」與「領域切分」混用</td>
          <td>取標題語意半邊為 canonical、其餘出現點改寫</td>
      </tr>
      <tr>
          <td>縮寫與全名並存當引用錨（「ops 階段」「操作維運階段」）</td>
          <td>全名為 canonical、縮寫只出現在已建立對應的同段內</td>
      </tr>
  </tbody>
</table>
<p>修法的判斷次序：canonical 字串取「標題的語意半邊」（標題是該單位的 fact 所在）；標題語意半邊太長不適合引用時、先改標題、再統一引用 — 標題改短是一次成本、引用各自簡寫是持續發散。</p>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<ul>
<li><a href="/blog/report/reference-by-semantic-title-not-number/" data-link-title="引用章節用語意標題、不用位置編號：編號是結構排列的 derivation、會隨版本漂移" data-link-desc="跨段落、跨檔引用結構單位（章節 / 階段 / 條列項）時、引用語意標題（副標題）、不引用位置編號（Stage 3、第 5 章、第 3 點）。編號是「目前結構排列」的 derivation、不是 fact；結構重排時編號全部位移、引用點不會報錯、而是 silent 指向錯的內容 — 比 broken link 更難偵測。標題的存在意義就是承載可被引用的語意。是 #44 SSoT 在結構引用維度的實例、#93 identifier-as-fact 家族的 sibling、#84 命名承載語意的引用面延伸。">#155 引用章節用語意標題、不用位置編號</a>：本卡補 #155 的缺口。#155 把錨點從編號換成語意名稱、預設了「語意名稱是單一的」；雙名讓 #155 的修法只完成一半 — 錨點穩定了、可比對性沒跟上。三卡合起來是完整的引用紀律：#155 管引用端（錨什麼）、#156 管命名端的內容（名稱不含 derivation）、本卡管命名端的唯一性（一個單位一個字串）。</li>
<li><a href="/blog/report/name-collections-by-role-not-count/" data-link-title="集合命名用角色、不內嵌數量：「核心七問」的七是成員數的 derivation、加一問就全面失真" data-link-desc="「核心七問」「成長六階段」「四大支柱」這類名稱把成員數量烤進名字裡 — 數量是集合當前成員的 derivation、不是集合的語意身分；成員增減時名稱失真、且名稱是被複製最多次的字串、缺陷隨每次引用繁殖。修法：命名只承載角色與層級（核心問題 / 次要問題 / 撞牆階段）、數量讓清單自己呈現。本卡是 #155 的命名端 sibling（#155 修引用端、本卡讓「語意標題是穩定錨」的前提真正成立）、#44 SSoT 在名稱內容的實例、#84 命名檢驗的數量維度。">#156 集合命名用角色、不內嵌數量</a>：sibling。#156 處理名稱「內容」的 fact 純度、本卡處理名稱「數量」的唯一性 — 一個名稱可以通過 #156（不含成員數）仍違反本卡（有同義變體）。</li>
<li><a href="/blog/report/single-source-of-truth/" data-link-title="Single Source of Truth：值的住址只能有一處" data-link-desc="同一個值（CSS token、視覺基準、runtime 量測）的權威來源只能有一個位置 — 多源時會分歧、會漏改、會讓讀者不知道哪個生效。本文是 #3 / #26 / #27 三篇實作的共同抽象。">#44 Single Source of Truth</a>：同一語意身分的兩個字串就是同一個 fact 的兩個源 — 比值的多源更隱蔽、因為兩個字串「語意上相等」、讀者看不出這是違規、只有工具比對才會痛。</li>
<li><a href="/blog/report/naming-as-iterated-artifact/" data-link-title="Naming 是 iterated artifact：第一個名字幾乎不對、四輪 review 才收斂" data-link-desc="命名（變數 / 函式 / 檔名 / slug / API endpoint）幾乎沒有「一次寫對」的可能：第一個名字基於當下狹窄的 context、會在後續 cross-call-site / grep / 重構中暴露錯位。命名的正確設計是 iterated — 寫第一版 → grep-ability 測試 → cross-call-site 一致性 → impl 洩漏 → 重命名。本卡是 #83 在「命名」場景的特化。">#84 Naming 是 iterated artifact</a>：#84 的輪 3 明文檢查「同一個概念在不同檔案用同名嗎」、判讀徵兆也列了「同概念出現兩個以上名字、選一個改另一個」— 本卡是這個檢查項在「結構單位的標題與引用」場景的應用：被檢查的從變數名換成章節 / 階段的語意錨、檢查動作相同。</li>
</ul>
<h2 id="觸發-case">觸發 case</h2>
<p>一份多階段訪談 skill 在一致性 audit 中被抓到兩處：階段標題寫「決策記錄 + scaffold 建議」、兩個協作檔的引用寫「決策收斂階段」；另一個階段的引用在「domain / event 切分」與「領域切分」之間混用。每個字串單獨檢查都合規（語意名、無編號、無數量）— audit reviewer 的原話是「語意可對上、但嚴格來說引用錨與標題語意半邊不同字串、下次重排時要靠人腦對應」。修法：標題語意半邊改成「決策收斂」、全 skill 引用統一；「領域切分」全部改寫為「domain / event 切分」。</p>
<h2 id="判讀徵兆">判讀徵兆</h2>
<ul>
<li>寫引用句時、回頭看目標標題：引用字串跟標題語意半邊逐字相同嗎？語意相同、字面不同 = 雙名。</li>
<li>跨檔 review 可操作：列出所有結構單位的語意半邊、<code>rg</code> 每個單位的引用句、引用字串不在清單內的逐處判讀。</li>
<li>同單位的引用在不同檔案用不同說法、且沒有任何一處寫明對應 — 雙名已經發生、統一的成本隨引用點數量成長。</li>
</ul>
]]></content:encoded></item></channel></rss>