<?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>Provider on Tarragon</title><link>https://tarrragon.github.io/blog/tags/provider/</link><description>Recent content in Provider on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/provider/index.xml" rel="self" type="application/rss+xml"/><item><title>App 永遠卡在載入畫面 — Riverpod 的 provider 是配方、容器才持有狀態</title><link>https://tarrragon.github.io/blog/work-log/flutter_riverpod_dual_container_state_desync/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/work-log/flutter_riverpod_dual_container_state_desync/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>觸發場景&lt;/strong>：Flutter App 啟動後永遠停在載入畫面。初始化的 provider 狀態永遠是 &lt;code>notStarted&lt;/code>、初始化流程從未執行——但初始化邏輯的單元測試是綠的
&lt;strong>疑問來源&lt;/strong>：&lt;code>main()&lt;/code> 裡明明呼叫了 &lt;code>initialize()&lt;/code>，為什麼 UI 看到的狀態完全沒動？
&lt;strong>整理目的&lt;/strong>：記下 Riverpod「provider 宣告全域、狀態屬於容器」的心智模型、以及跨容器操作靜默失效的機制
&lt;strong>本文邊界&lt;/strong>：素材是該專案 v0.10.1 的修復記錄（Riverpod 2.x 時期）；「配方 vs 廚房」的心智模型跨版本成立&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="症狀與現場">症狀與現場&lt;/h2>
&lt;p>出問題的 &lt;code>main()&lt;/code> 長這樣：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">final&lt;/span> &lt;span class="n">container&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ProviderContainer&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">container&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">appInitializationProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">notifier&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">initialize&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 對外部容器操作
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">runApp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ProviderScope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">child:&lt;/span> &lt;span class="n">BookLibraryApp&lt;/span>&lt;span class="p">()));&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">UI&lt;/span> &lt;span class="err">在另一個容器裡&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩行各自都「正確」：&lt;code>initialize()&lt;/code> 真的被呼叫、真的執行完；&lt;code>ProviderScope&lt;/code> 裡的 UI 真的在監聽 &lt;code>appInitializationProvider&lt;/code>。但 UI 的狀態永遠是 &lt;code>notStarted&lt;/code>——因為&lt;strong>它們操作的是兩份不同的狀態&lt;/strong>。&lt;/p>
&lt;h2 id="機制provider-宣告是配方容器持有狀態">機制：provider 宣告是配方、容器持有狀態&lt;/h2>
&lt;p>Riverpod 的 provider 宣告寫在全域：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">final&lt;/span> &lt;span class="n">appInitializationProvider&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NotifierProvider&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(...);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>全域宣告製造了「狀態也是全域」的錯覺。實際上這個全域物件只是&lt;strong>配方&lt;/strong>——描述「這個狀態怎麼建、怎麼變化」。狀態本身活在容器裡：&lt;code>ProviderContainer()&lt;/code> 建一個容器、&lt;code>ProviderScope&lt;/code> 在 widget tree 裡也建一個容器（它內部就是包了一個 container）。同一份配方在兩個容器裡各煮出一份互不相干的狀態。&lt;/p>
&lt;p>於是 &lt;code>container.read(...).initialize()&lt;/code> 改的是外部容器那份狀態——改成功了、沒有任何錯誤；UI 監聽的是 Scope 容器那份——從頭到尾沒人動它。&lt;strong>跨容器操作不會報錯、它只是安靜地作用在你以為之外的地方&lt;/strong>，這是這類 bug 難查的原因：每一段程式碼單獨看都在正常工作。&lt;/p>
&lt;h2 id="修法把觸發點移進唯一的容器">修法：把觸發點移進唯一的容器&lt;/h2>
&lt;p>修復選的方案是移除外部容器、讓初始化在 widget tree 內觸發：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// main.dart：只負責 runApp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">WidgetsFlutterBinding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ensureInitialized&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">runApp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">const&lt;/span> &lt;span class="n">ProviderScope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">child:&lt;/span> &lt;span class="n">BookLibraryApp&lt;/span>&lt;span class="p">()));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">// _AppWrapper：偵測未初始化、在首幀後觸發
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">initState&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">status&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">AppInitializationStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">notStarted&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">WidgetsBinding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">instance&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">addPostFrameCallback&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">ref&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">appInitializationProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">notifier&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">initialize&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩個細節。&lt;code>addPostFrameCallback&lt;/code> 把觸發延到首幀之後——build 期間直接改 provider 狀態是另一類錯誤（widget tree 建置中修改 provider 會炸）。觸發條件掛在 &lt;code>notStarted&lt;/code> 狀態上，讓「誰負責啟動初始化」有唯一答案：看到未初始化的第一個 wrapper。&lt;/p>
&lt;p>如果情境真的需要在 &lt;code>runApp&lt;/code> 之前操作 provider（例如讀取啟動設定），正解是讓兩邊共用同一個容器——自建的 container 透過 &lt;code>UncontrolledProviderScope&lt;/code> 傳進 widget tree，而不是各建一個。判準收成一句：&lt;strong>一個 App 裡活著的容器數量應該是一、每多一個都要能說出它為什麼必須隔離&lt;/strong>（測試裡的 &lt;code>ProviderContainer(overrides: ...)&lt;/code> 就是正當的隔離）。&lt;/p>
&lt;h2 id="為什麼單元測試沒抓到">為什麼單元測試沒抓到&lt;/h2>
&lt;p>初始化 Notifier 的單元測試是綠的——它驗證「這份配方煮出來的狀態會正確走完初始化」，在測試自己的容器裡完全成立。bug 不在配方、在&lt;strong>佈線&lt;/strong>：兩個容器的存在是 &lt;code>main()&lt;/code> 的組裝問題，單元測試的邊界不含組裝。這類 bug 的守備範圍在整合層——一條「啟動 App、斷言最終離開載入畫面」的 widget 測試就能攔住。修復記錄還留了一個測試環境的絆腳石：初始化流程裡的 &lt;code>Future.delayed&lt;/code> 計時器跟 widget test 的 pending-timer 檢查不相容，這也是當時整合測試缺席的原因之一——計時器類的延遲在可測性上要優先考慮可注入的 clock 或可等待的 Future。&lt;/p>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;ul>
&lt;li>「狀態改了但 UI 沒反應」且雙方程式碼各自看都正確——先數容器：全專案搜 &lt;code>ProviderContainer(&lt;/code>，每一個實例都問「它跟 UI 的 Scope 是同一個嗎」&lt;/li>
&lt;li>&lt;code>main()&lt;/code> 裡出現 &lt;code>ProviderContainer()&lt;/code> 又出現 &lt;code>ProviderScope&lt;/code>——幾乎就是本文的 bug 形態&lt;/li>
&lt;li>provider 狀態「永遠是初始值」——比「狀態錯誤」更指向無人操作這份實例、該查操作方作用在哪個容器&lt;/li>
&lt;/ul>
&lt;h2 id="相關閱讀">相關閱讀&lt;/h2>
&lt;ul>
&lt;li>同屬狀態管理框架的跨界 bug：&lt;a href="https://tarrragon.github.io/blog/work-log/dart_test_getx_cross_file_state_pollution/" data-link-title="Dart test 的跨檔案 GetX 狀態污染：flaky 真因不是 fail 訊息上的那個 test" data-link-desc="`flutter test` 整套跑隨機 fail、單獨跑該 file 卻 100% 過。根因是 dart test runner 同 process 內 GetX state 跨 file 污染，fail 位置看 `&amp;#43;N -1` 累計而非訊息標示的 test。">Dart test 的跨檔案 GetX 狀態污染&lt;/a>——GetX 的問題是全域單例讓狀態「太共享」、Riverpod 這裡是容器隔離讓狀態「太不共享」，兩篇合看是狀態作用域的兩個失敗方向&lt;/li>
&lt;li>靜默失效的同構：&lt;a href="https://tarrragon.github.io/blog/report/lint-scope-must-be-explicit-fact/" data-link-title="檢查規則的作用域要顯式列舉：零 error 可能是沒被檢查" data-link-desc="新增與既有受檢目錄同類的內容目錄時、或工具鏈長期零 error 卻累積出違規時使用。規則的作用域由路徑常數決定、該常數常同時被多個檢查共用，擴作用域會連帶擴語意；作用域是獨立於規則內容的 fact，驗收方式是先確認新規則對已知違規報錯。">#221 檢查規則的作用域要顯式列舉&lt;/a>——作用在錯的作用域、不產生任何錯誤訊號，症狀在遠處浮現&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<blockquote>
<p><strong>觸發場景</strong>：Flutter App 啟動後永遠停在載入畫面。初始化的 provider 狀態永遠是 <code>notStarted</code>、初始化流程從未執行——但初始化邏輯的單元測試是綠的
<strong>疑問來源</strong>：<code>main()</code> 裡明明呼叫了 <code>initialize()</code>，為什麼 UI 看到的狀態完全沒動？
<strong>整理目的</strong>：記下 Riverpod「provider 宣告全域、狀態屬於容器」的心智模型、以及跨容器操作靜默失效的機制
<strong>本文邊界</strong>：素材是該專案 v0.10.1 的修復記錄（Riverpod 2.x 時期）；「配方 vs 廚房」的心智模型跨版本成立</p></blockquote>
<hr>
<h2 id="症狀與現場">症狀與現場</h2>
<p>出問題的 <code>main()</code> 長這樣：</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="kd">final</span> <span class="n">container</span> <span class="o">=</span> <span class="n">ProviderContainer</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">container</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="n">appInitializationProvider</span><span class="p">.</span><span class="n">notifier</span><span class="p">).</span><span class="n">initialize</span><span class="p">();</span>  <span class="c1">// 對外部容器操作
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">runApp</span><span class="p">(</span><span class="n">ProviderScope</span><span class="p">(</span><span class="nl">child:</span> <span class="n">BookLibraryApp</span><span class="p">()));</span>                    <span class="o">//</span> <span class="n">UI</span> <span class="err">在另一個容器裡</span></span></span></code></pre></div><p>兩行各自都「正確」：<code>initialize()</code> 真的被呼叫、真的執行完；<code>ProviderScope</code> 裡的 UI 真的在監聽 <code>appInitializationProvider</code>。但 UI 的狀態永遠是 <code>notStarted</code>——因為<strong>它們操作的是兩份不同的狀態</strong>。</p>
<h2 id="機制provider-宣告是配方容器持有狀態">機制：provider 宣告是配方、容器持有狀態</h2>
<p>Riverpod 的 provider 宣告寫在全域：</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="kd">final</span> <span class="n">appInitializationProvider</span> <span class="o">=</span> <span class="n">NotifierProvider</span><span class="o">&lt;</span><span class="p">...</span><span class="o">&gt;</span><span class="p">(...);</span></span></span></code></pre></div><p>全域宣告製造了「狀態也是全域」的錯覺。實際上這個全域物件只是<strong>配方</strong>——描述「這個狀態怎麼建、怎麼變化」。狀態本身活在容器裡：<code>ProviderContainer()</code> 建一個容器、<code>ProviderScope</code> 在 widget tree 裡也建一個容器（它內部就是包了一個 container）。同一份配方在兩個容器裡各煮出一份互不相干的狀態。</p>
<p>於是 <code>container.read(...).initialize()</code> 改的是外部容器那份狀態——改成功了、沒有任何錯誤；UI 監聽的是 Scope 容器那份——從頭到尾沒人動它。<strong>跨容器操作不會報錯、它只是安靜地作用在你以為之外的地方</strong>，這是這類 bug 難查的原因：每一段程式碼單獨看都在正常工作。</p>
<h2 id="修法把觸發點移進唯一的容器">修法：把觸發點移進唯一的容器</h2>
<p>修復選的方案是移除外部容器、讓初始化在 widget tree 內觸發：</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">// main.dart：只負責 runApp
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">WidgetsFlutterBinding</span><span class="p">.</span><span class="n">ensureInitialized</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">ProviderScope</span><span class="p">(</span><span class="nl">child:</span> <span class="n">BookLibraryApp</span><span class="p">()));</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// _AppWrapper：偵測未初始化、在首幀後觸發
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="n">initState</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">AppInitializationStatus</span><span class="p">.</span><span class="n">notStarted</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="n">WidgetsBinding</span><span class="p">.</span><span class="n">instance</span><span class="p">.</span><span class="n">addPostFrameCallback</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">ref</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="n">appInitializationProvider</span><span class="p">.</span><span class="n">notifier</span><span class="p">).</span><span class="n">initialize</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>兩個細節。<code>addPostFrameCallback</code> 把觸發延到首幀之後——build 期間直接改 provider 狀態是另一類錯誤（widget tree 建置中修改 provider 會炸）。觸發條件掛在 <code>notStarted</code> 狀態上，讓「誰負責啟動初始化」有唯一答案：看到未初始化的第一個 wrapper。</p>
<p>如果情境真的需要在 <code>runApp</code> 之前操作 provider（例如讀取啟動設定），正解是讓兩邊共用同一個容器——自建的 container 透過 <code>UncontrolledProviderScope</code> 傳進 widget tree，而不是各建一個。判準收成一句：<strong>一個 App 裡活著的容器數量應該是一、每多一個都要能說出它為什麼必須隔離</strong>（測試裡的 <code>ProviderContainer(overrides: ...)</code> 就是正當的隔離）。</p>
<h2 id="為什麼單元測試沒抓到">為什麼單元測試沒抓到</h2>
<p>初始化 Notifier 的單元測試是綠的——它驗證「這份配方煮出來的狀態會正確走完初始化」，在測試自己的容器裡完全成立。bug 不在配方、在<strong>佈線</strong>：兩個容器的存在是 <code>main()</code> 的組裝問題，單元測試的邊界不含組裝。這類 bug 的守備範圍在整合層——一條「啟動 App、斷言最終離開載入畫面」的 widget 測試就能攔住。修復記錄還留了一個測試環境的絆腳石：初始化流程裡的 <code>Future.delayed</code> 計時器跟 widget test 的 pending-timer 檢查不相容，這也是當時整合測試缺席的原因之一——計時器類的延遲在可測性上要優先考慮可注入的 clock 或可等待的 Future。</p>
<h2 id="判讀徵兆">判讀徵兆</h2>
<ul>
<li>「狀態改了但 UI 沒反應」且雙方程式碼各自看都正確——先數容器：全專案搜 <code>ProviderContainer(</code>，每一個實例都問「它跟 UI 的 Scope 是同一個嗎」</li>
<li><code>main()</code> 裡出現 <code>ProviderContainer()</code> 又出現 <code>ProviderScope</code>——幾乎就是本文的 bug 形態</li>
<li>provider 狀態「永遠是初始值」——比「狀態錯誤」更指向無人操作這份實例、該查操作方作用在哪個容器</li>
</ul>
<h2 id="相關閱讀">相關閱讀</h2>
<ul>
<li>同屬狀態管理框架的跨界 bug：<a href="/blog/work-log/dart_test_getx_cross_file_state_pollution/" data-link-title="Dart test 的跨檔案 GetX 狀態污染：flaky 真因不是 fail 訊息上的那個 test" data-link-desc="`flutter test` 整套跑隨機 fail、單獨跑該 file 卻 100% 過。根因是 dart test runner 同 process 內 GetX state 跨 file 污染，fail 位置看 `&#43;N -1` 累計而非訊息標示的 test。">Dart test 的跨檔案 GetX 狀態污染</a>——GetX 的問題是全域單例讓狀態「太共享」、Riverpod 這裡是容器隔離讓狀態「太不共享」，兩篇合看是狀態作用域的兩個失敗方向</li>
<li>靜默失效的同構：<a href="/blog/report/lint-scope-must-be-explicit-fact/" data-link-title="檢查規則的作用域要顯式列舉：零 error 可能是沒被檢查" data-link-desc="新增與既有受檢目錄同類的內容目錄時、或工具鏈長期零 error 卻累積出違規時使用。規則的作用域由路徑常數決定、該常數常同時被多個檢查共用，擴作用域會連帶擴語意；作用域是獨立於規則內容的 fact，驗收方式是先確認新規則對已知違規報錯。">#221 檢查規則的作用域要顯式列舉</a>——作用在錯的作用域、不產生任何錯誤訊號，症狀在遠處浮現</li>
</ul>
]]></content:encoded></item></channel></rss>